Interface for HTTP webservers, frameworks and clients.
See Raxx.Kit for a project generator that helps you set upa web project based on Raxx/Ace.
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
GET /
has path []
.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)
handle_request/2
callback.In this example it is unused and so set to nil.Start your project and visit http://localhost:8080.
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
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
[]
;and the new state of the server, in this case no change state
.initial_state
is configured when the server is started.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
set_body(true)
marks that the response has a body that it is not yet known.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
handle_data
will be invoked as each part arrives.An application should never assume how a body will be broken into data parts.It is worth noting what guarantees are given on the request parts passed to theServer's handle_*
functions. It depends on the Server type,Raxx.Server
vs Raxx.SimpleServer
:
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:
Any Raxx.Middleware
s should follow the same logic.
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