raxx v1.0.0 Raxx.Server behaviour

Interface to handle server side communication in an HTTP message exchange.

If simple request -> response transformation is possible, try Raxx.SimpleServer

A module implementing Raxx.Server is run by an HTTP server. For example Ace can run such a module for both HTTP/1.x and HTTP/2 exchanges

Getting Started

Send complete response as soon as request headers are received.

defmodule SimpleServer do
  use Raxx.Server

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

Store data as it is available from a clients request

defmodule StreamingRequest do
  use Raxx.Server

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

  def handle_data(body, state = {:file, device}) do
    IO.write(device, body)
    {[], state}
  end

  def handle_tail(_trailers, state) do
    response(:see_other)
    |> set_header("location", "/")
  end
end

Subscribe server to event source and forward notifications to client.

defmodule SubscribeToMessages do
  use Raxx.Server

  def handle_head(_request, _state) do
    {:ok, _} = ChatRoom.join()
    response(:ok)
    |> set_header("content-type", "text/event-stream")
    |> set_body(true)
  end

  def handle_info({ChatRoom, data}, state) do
    {[body(data)], state}
  end
end

Notes

  • handle_head/2 will always be called with a request that has body as a boolean. For small requests where buffering the whole request is acceptable a simple middleware can be used.
  • Acceptable return values are the same for all callbacks; either a Raxx.Response, which must be complete or a list of message parts and a new state.

Streaming

Raxx.Server defines an interface to stream the body of request and responses.

This has several advantages:

  • Large payloads do not need to be help in memory
  • Server can push information as it becomes available, using Server Sent Events.
  • If a request has invalid headers then a reply can be set without handling the body.
  • Content can be generated as requested using HTTP/2 flow control

The body of a Raxx message (Raxx.Request or Raxx.Response) may be one of three types:

  • io_list - This is the complete body for the message.
  • :false - There is no body, for example :GET requests never have a body.
  • :true - There is a body, it can be processed as it is received

Server Isolation

To start an exchange a client sends a request. The server, upon receiving this message, sends a reply. A logical HTTP exchange consists of a single request and response.

Methods such as pipelining and multiplexing combine multiple logical exchanges onto a single connection. This is done to improve performance and is a detail not exposed a server.

A Raxx server handles a single HTTP exchange. Therefore a single connection my have multiple servers each isolated in their own process.

Termination

An exchange can be stopped early by terminating the server process. Support for early termination is not consistent between versions of HTTP.

  • HTTP/2: server exit with reason :normal, stream reset with error CANCEL.
  • HTTP/2: server exit any other reason, stream reset with error INTERNAL_ERROR.
  • HTTP/1.x: server exit with any reason, connection is closed.

Raxx.Server does not provide a terminate callback. Any cleanup that needs to be done from an aborted exchange should be handled by monitoring the server process.

Link to this section Summary

Types

Possible return values instructing server to send client data and update state if appropriate.

State of application server.

t()

The behaviour and state of a raxx server

Functions

Execute a server module and current state in response to a new message

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", binary data and returns the whole server, not just its state.

Similar to Raxx.Server.handle/2, except it only accepts Raxx.Request.t/0 and returns the whole server, not just its state.

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", trailers and returns the whole server, not just its state.

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", trailers and returns the whole server, not just its state.

Verify server can be run?

Callbacks

Called every time data from the request body is received

Called once when a client starts a stream,

Called for all other messages the server may recieve

Called once when a request finishes.

Link to this section Types

Link to this type

next()
next() :: {[Raxx.part()], state()} | Raxx.Response.t()

Possible return values instructing server to send client data and update state if appropriate.

Link to this type

state()
state() :: any()

State of application server.

Original value is the configuration given when starting the raxx application.

Link to this type

t()
t() :: {module(), state()}

The behaviour and state of a raxx server

Link to this section Functions

Link to this function

handle(arg, request)
handle(t(), term()) :: {[Raxx.part()], state()}

Execute a server module and current state in response to a new message

Link to this function

handle_data(arg, data)
handle_data(t(), binary()) :: {[Raxx.part()], t()}

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", binary data and returns the whole server, not just its state.

Raxx.Server.handle/2 uses the data structures sent from the http server, whereas Raxx.Server.handle_* use the "unpacked" data, in the shape defined by callbacks.

Link to this function

handle_head(arg, request)
handle_head(t(), Raxx.Request.t()) :: {[Raxx.part()], t()}

Similar to Raxx.Server.handle/2, except it only accepts Raxx.Request.t/0 and returns the whole server, not just its state.

Raxx.Server.handle/2 uses the data structures sent from the http server, whereas Raxx.Server.handle_* use the "unpacked" data, in the shape defined by callbacks.

Link to this function

handle_info(arg, info)
handle_info(t(), any()) :: {[Raxx.part()], t()}

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", trailers and returns the whole server, not just its state.

Raxx.Server.handle/2 uses the data structures sent from the http server, whereas Raxx.Server.handle_* use the "unpacked" data, in the shape defined by callbacks.

Link to this function

handle_tail(arg, tail)
handle_tail(t(), [{binary(), binary()}]) :: {[Raxx.part()], t()}

Similar to Raxx.Server.handle/2, except it only accepts the "unpacked", trailers and returns the whole server, not just its state.

Raxx.Server.handle/2 uses the data structures sent from the http server, whereas Raxx.Server.handle_* use the "unpacked" data, in the shape defined by callbacks.

Link to this function

verify_server(arg)

Verify server can be run?

A runnable server consists of a tuple of server module and initial state. The server module must implement this modules behaviour. The initial state can be any term

Examples

# Could just call verify
iex> Raxx.Server.verify_server({Raxx.ServerTest.DefaultServer, %{}})
{:ok, {Raxx.ServerTest.DefaultServer, %{}}}

iex> Raxx.Server.verify_server({GenServer, %{}})
{:error, {:not_a_server_module, GenServer}}

iex> Raxx.Server.verify_server({NotAModule, %{}})
{:error, {:not_a_module, NotAModule}}

Link to this section Callbacks

Link to this callback

handle_data(binary, state)
handle_data(binary(), state()) :: next()

Called every time data from the request body is received

Link to this callback

handle_head(arg1, state)
handle_head(Raxx.Request.t(), state()) :: next()

Called once when a client starts a stream,

Passed a Raxx.Request and server configuration. Note the value of the request body will be a boolean.

This callback can be relied upon to execute before any other callbacks

Link to this callback

handle_info(any, state)
handle_info(any(), state()) :: next()

Called for all other messages the server may recieve

Link to this callback

handle_tail(list, state)
handle_tail([{binary(), binary()}], state()) :: next()

Called once when a request finishes.

This will be called with an empty list of headers is request is completed without trailers.

Will not be called at all if the Raxx.Request.t/0 struct passed to handle_head/2 had body: false.