Raxx: an Elixir webserver interface
What is Raxx?
- An interface specification for Elixir webservers and Elixir application.
- A set of tools to help develop Raxx-compliant web applications
Raxx is inspired by the Ruby's Rack interface and Clojure's Ring interface.
[Documentation for Raxx is available online](TODO hex)
Usage
Raxx handlers
A Raxx handler is a module that has a handle_request function.
It takes two arguments, a raxx request and an application specific environment.
The return value is a map with three keys, the status, the headers, and the body.
Minimal
With the power of Elixirs pattern matching against maps it is possible to handle request routing without a dsl.
defmodule BasicRouter do
# handle the root path
def handle_request(%{path: [], method: "GET"}, _env) do
%{status: 200, headers: %{}, body: "Hello, World!"}
end
# forward to a sub router
def handle_request(request = %{path: ["api" | rest]}, env) do
ApiRouter.handle_request(%{request | path: rest}, env)
end
# handle a variable segment in path
def handle_request(%{path: ["greet", name], method: "GET"}, _env) do
%{status: 200, headers: %{}, body: "Hello, #{name}"}
end
end
Manually creating all these response hashes can be tedious so the Response module has helpers.
defmodule FooRouter do
import Raxx.Response
def handle_request(%{path: ["users"], method: "GET"}, _env) do
ok("All user: Andy, Bethany, Clive")
end
def handle_request(%{path: ["users"], method: "POST", body: data}, _env) do
case MyApp.create_user(data) do
{:ok, user} -> created("New user #{user}")
{:error, :already_exists} -> conflict("sorry")
{:error, :bad_params} -> bad_request("sorry")
{:error, :database_fail} -> bad_gateway("sorry")
{:error, _unknown} -> internal_server_error("Well thats weird")
end
end
def handle_request(%{path: ["users"], method: _}, _env) do
method_not_allowed("Don't do that")
end
def handle_request(%{path: ["users", id], method: "GET"}, _env) do
case MyApp.get_user(id) do
{:ok, user} -> ok("New user #{user}")
{:error, nil} -> not_found("User unknown")
{:error, :deleted} -> gone("User deleted")
end
end
def handle_request(_request, _env) do
not_found("Sorry didn't get that")
end
end
Raxx Server Sent Events
See sever sent events in examples directory.
defmodule ServerSentEvents.Router do
import Raxx.Response
# Can't use ServerSentEvents Handler in same module as other Streaming handlers.
import Raxx.ServerSentEvents
def handle_request(%{path: [], method: "GET"}, _opts) do
ok(home_page)
end
def handle_request(%{path: ["events"], method: "GET"}, opts) do
upgrade(opts, __MODULE__)
end
def handle_request(_request, _opts) do
not_found("Page not found")
end
def handle_upgrade(_options) do
Process.send_after(self, 0, 1000)
event("hello")
end
def handle_info(10, _opts) do
close()
end
def handle_info(i, _opts) when rem(i, 2) == 0 do
Process.send_after(self, i + 1, 1000)
event(Integer.to_string(i))
end
def handle_info(i, _opts) do
Process.send_after(self, i + 1, 1000)
no_event
end
defp home_page do
"""
The page. see example.
"""
end
end
Some outstanding questions about Server Sent Events functionality.
- Disallow event of type error.
- Handle long poll pollyfill.
- Raxx client.
- Any shared functionality with file streaming, long pole.
- What to do if message handler throws error.
Link to implementing server in node.js
Installation
If available in Hex, the package can be installed as:
Add raxx to your list of dependencies in
mix.exs:def deps do [{:raxx, "~> 0.0.1"}] end
Raxx apps/routers needs to be mounted Elixir/erlang server using one of the provided adapters. Instructions for this are found in each adapters README
- [cowboy](https://github.com/CrowdHailer/raxx/tree/master/example/cowboy_example). Currently just follow example.
Contributing
If you have Elixir installed on your machine then you can treat this project as a normal mix project and run tests via mix test.
If required a development environment can be created using Vagrant.
Principles
- Stateless HTTP request fulfill a valuable role in modern applications and will continue to do so.
- Handling other communication patterns as plug intends to do just adds complexity which is unnecessary on a whole class of applications.
- Use Ruby rack and Clojure ring as inspiration for naming but be happy to break away from historic CGI-style header names.
- Surface utilities so that it can be used in general HTTP based applications
- Only return json if json is asked for.
- Your server as a function