Skip to content

Latest commit

 

History

History
188 lines (127 loc) · 6.88 KB

web_servers.livemd

File metadata and controls

188 lines (127 loc) · 6.88 KB

Web Servers

Mix.install([
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:tested_cell, github: "brooklinjazz/tested_cell"},
  {:utils, path: "#{__DIR__}/../utils"},
  {:httpoison, "~> 1.8"}
])

Navigation

Return Home Report An Issue

Setup

Ensure you type the ea keyboard shortcut to evaluate all Elixir cells before starting. Alternatively you can evaluate the Elixir cells as you read.

Overview

Web development is one sub-field within programming. Broadly speaking, web developers create websites.

Websites are programs that run on a web server. Clients communicate with these web servers over a computer network. We've already seen how we can connect to a web server as a client using HTTP requests in the APIs section.

In this lesson, we'll cover how we can create the web server that the client connects to. Together, we'll create a web server from scratch to demystify how they are built.

However, be aware that in general we'll rely on the Phoenix Framework, which is a web framework for Elixir, rather than building our own web server from scratch.

Also, this lesson relies on port 4000 being free. If you have any programs using port 4000 this lesson may not work properly.

Transmission Control Protocol (TCP)

Transmission Control Protocol (TCP) is the communication protocol applications use to communicate and exchange data over a network.

We use the built in :gen_tcp library to from Erlang to start a server that uses TCP to listen for connections on a network.

This server creates a socket connection on a specified port of the machine. A port is a communication endpoint for a particular service. For example, by default this livebook application runs on port 8080 which you can see in the URL of your browser, http://localhost:8080/sessions/. A socket allows for two way communication over this port to send and receive data.

graph LR;
  S[Server]
  P[Port]
  LSO[Listen Socket]
  C[Client]
  S --listen on --> P --establish--> LSO
  C --request --> LSO
  LSO --response--> C
Loading

This socket listens for connections from clients, and accepts them when they connect, reads the request, then send a response to the client.

sequenceDiagram
participant C as Client
participant S as Server
S->> S: start socket
C--> S: accept connection
C->> S: send request
S->> S: read request
S->> C: send response
C--> S: close connection
Loading

Below, we simulate a web server that accepts only a single request, and returns "Hello, world!" to the client. We then send the web server a HTTPoison.get!/3 request and receive a response from the server.

port = 4000

spawn(fn ->
  # start socket
  {:ok, listen_socket} = :gen_tcp.listen(port, active: false, reuseaddr: true)

  # accept connection
  {:ok, connection} = :gen_tcp.accept(listen_socket)

  # read request
  {:ok, request} = :gen_tcp.recv(connection, 0)
  IO.inspect(request, label: "Client Request")

  # send response
  response = "HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n Hello world!"
  :gen_tcp.send(connection, response)

  # close connection
  :gen_tcp.close(connection)

  # we kill the listen_socket to avoid address already in use issues.
  # Typically, the listen socket will remain open and recursively accept connections indefinitely.
  Process.exit(listen_socket, :kill)
end)

# send request
HTTPoison.get!("http://localhost:#{port}")

Web Server

Putting all of this together, we can make a WebServer module. The WebServer module will listen on port 4000 and recursively accept client requests indefinitely.

Start the WebServer socket below by executing the code cell, then navigate to http://localhost:4000 in a new tab in your browser. You should see a message from the web server with the current time.

defmodule WebServer do
  def start(port) do
    {:ok, listen_socket} = :gen_tcp.listen(port, active: false, reuseaddr: true)

    accept(listen_socket)
  end

  def accept(listen_socket) do
    # accept connection
    {:ok, connection} = :gen_tcp.accept(listen_socket)

    # send response
    current_time = Time.to_string(Time.utc_now())
    response = "HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n It is #{current_time}"
    :gen_tcp.send(connection, response)

    # close connection
    :gen_tcp.close(connection)

    accept(listen_socket)
  end
end

WebServer.start(4000)

This server will remain open indefinitely and recursively call accept/2 every time a new client connects to the socket on port 4000.

All websites use a similar (albeit more fully featured) web server to accept client connections, receive requests, and send responses back.

Hopefully, this example provides you context on how web servers operate under the hood. However, it's rarely practical to build our own web server, and instead we'll rely on the Phoenix Framework which hides these implementation details from use.

Your Turn

We've seen a simple text response, but web servers are also able to send more complex responses containing an entire web page.

In the WebServer module above, modify the original response:

response = "HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n It is #{current_time}"

To a response using Hyper Text Markup Language (HTML). HTML is the code used to structure a web page. For example, we can modify the response to use an HTML header tag <h>.

response = "HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n <h>It is #{current_time}<h/>"

Visit http://localhost:4000 to see your modified content. You may need to stop the WebServer Elixir cell and start it again.

Commit Your Progress

Run the following in your command line from the beta_curriculum folder to track and save your progress in a Git commit.

$ git add .
$ git commit -m "finish web servers section"

Up Next

Previous Next
Pokemon API Phoenix