Raxx

Interface for HTTP webservers, frameworks and clients.

Hex pmBuild StatusLicense

Simple Client

A very simple HTTP/1.1 client.

request = Raxx.request(:GET, "http://example.com")
|> Raxx.set_header("accept", "application/json")

{:ok, response} = Raxx.SimpleClient.send_sync(request, 2000)

See Raxx.SimpleClient for full documentation.

Server Extensions

This project includes:

Additional utilities that can be used in Raxx applications.

Getting started

HTTP is an exchange where a client sends a request to a server and expects a response. At its simplest this can be viewed as follows

Simple client server exchange.

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

Simple server

The HomePage server implements the simplest HTTP message exchange. The complete response is constructed from the request.

defmodule HomePage do
  use Raxx.Server

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

Running a server

To start a web service a Raxx compatable server is needed. For example Ace.

server = HomePage
initial_state = %{}
options = [port: 8080, cleartext: true]

{:ok, pid} = Ace.HTTP.Service.start_link({server, initial_state}, options)

Visit http://localhost:8080.

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_request(%{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

Streaming

Any client server exchange is actually a stream of information in either direction. Raxx.Server provides callbacks to proccess parts of a stream as they are received.

Detailed view of client server exchange.

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

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

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

Logger

The Raxx.Logger can be used in any raxx server module to add basic logs. The format of the logs matches the format of the basic Plug logger.