Home

Awesome

Raxx

Interface for HTTP webservers, frameworks and clients.

Hex pm Build Status License

See Raxx.Kit for a project generator that helps you set up a web project based on Raxx/Ace.

Simple server

1. Defining a server

defmodule MyServer do
  use Raxx.SimpleServer

  @impl Raxx.SimpleServer
  def handle_request(%{method: :GET, path: []}, _state) do
    response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body("Hello, World!")
  end

  def handle_request(%{method: :GET, path: _}, _state) do
    response(:not_found)
    |> set_header("content-type", "text/plain")
    |> set_body("Oops! Nothing here.")
  end
end

2. Running a server

To start a Raxx server a compatible HTTP server is needed. This example uses Ace that can serve both HTTP/1 and HTTP/2.

raxx_server = {MyServer, nil}
http_options = [port: 8080, cleartext: true]

{:ok, pid} = Ace.HTTP.Service.start_link(raxx_server, http_options)

Start your project and visit http://localhost:8080.

HTTP streaming

An HTTP exchange involves a client sending data to a server receiving a response. A simple view is to model this as a single message sent in each direction. Working with this model corresponds to Raxx.SimpleServer callbacks.

           request -->
Client ============================================ Server
                                   <-- response

When the simple model is insufficient Raxx exposes a lower model. This consists of a series of messages in each direction. Working with this model corresponds to Raxx.Server callbacks.

           tail | data(1+) | head(request) -->
Client ============================================ Server
           <-- head(response) | data(1+) | tail

Stateful server

The LongPoll server is stateful. After receiving a complete request this server has to wait for extra input before sending a response to the client.

defmodule LongPoll do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :GET, path: ["slow"]}, state) do
    Process.send_after(self(), :reply, 30_000)

    {[], state}
  end

  @impl Raxx.Server
  def handle_info(:reply, _state) do
    response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body("Hello, Thanks for waiting.")
  end
end

Server streaming

The SubscribeToMessages server streams its response. The server will send the head of the response upon receiving the request. Data is sent to the client, as part of the body, when it becomes available. The response is completed when the chatroom sends a :closed message.

defmodule SubscribeToMessages do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :GET, path: ["messages"]}, state) do
    {:ok, _} = ChatRoom.join()
    outbound = response(:ok)
    |> set_header("content-type", "text/plain")
    |> set_body(true)

    {[outbound], state}
  end

  @impl Raxx.Server
  def handle_info({ChatRoom, :closed}, state) do
    outbound = tail()

    {[outbound], state}
  end

  def handle_info({ChatRoom, data}, state) do
    outbound = data(data)

    {[outbound], state}
  end
end

Client streaming

The Upload server writes data to a file as it is received. Only once the complete request has been received is a response sent.

defmodule Upload do
  use Raxx.Server

  @impl Raxx.Server
  def handle_head(%{method: :PUT, path: ["upload"] body: true}, _state) do
    {:ok, io_device} = File.open("my/path")
    {[], {:file, device}}
  end

  @impl Raxx.Server
  def handle_data(data, state = {:file, device}) do
    IO.write(device, data)
    {[], state}
  end

  @impl Raxx.Server
  def handle_tail(_trailers, state) do
    response(:see_other)
    |> set_header("location", "/")
  end
end

Request/Response flow

It is worth noting what guarantees are given on the request parts passed to the Server's handle_* functions. It depends on the Server type, Raxx.Server vs Raxx.SimpleServer:

<!-- NOTE: diagram svg files contain the source diagram and can be edited using draw.io -->

request flow

So, for example, after a %Raxx.Request{body: false} is passed to a Server's c:Raxx.Server.handle_head/2 callback, no further request parts will be passed to to the server (c:Raxx.Server.handle_info/2 messages might be, though).

Similarly, these are the valid sequences of the response parts returned from the Servers:

<!-- NOTE: diagram svg files contain the source diagram and can be edited using draw.io -->

response flow

Any Raxx.Middlewares should follow the same logic.

Router

The Raxx.Router can be used to match requests to specific server modules.

defmodule MyApp do
  use Raxx.Server

  use Raxx.Router, [
    {%{method: :GET, path: []}, HomePage},
    {%{method: :GET, path: ["slow"]}, LongPoll},
    {%{method: :GET, path: ["messages"]}, SubscribeToMessages},
    {%{method: :PUT, path: ["upload"]}, Upload},
    {_, NotFoundPage}
  ]
end