Packages
raxx
0.4.3
1.1.0
1.0.1
1.0.0
1.0.0-rc.3
1.0.0-rc.2
retired
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.18.1
0.18.0
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
retired
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.14
0.14.13
0.14.12
0.14.11
0.14.10
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
0.0.1
Interface for HTTP webservers, frameworks and clients.
Current section
Files
Jump to
Current section
Files
README.md
# Raxx: an Elixir webserver interface
## What is Raxx?
1. An interface specification for Elixir webservers and Elixir application.
2. A set of tools to help develop Raxx-compliant web applications
[Documentation for Raxx is available online](https://hexdocs.pm/raxx)
[Introductory talk I gave at Elixir.LDN](https://www.youtube.com/watch?v=80AXtvXFIA4&index=2&list=PLWbHc_FXPo2ivlIjzcaHS9N_Swe_0hWj0)
## Hello, World!
Add raxx to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:raxx, "~> 0.4.3"}]
end
```
Define the behaviour of your servers.
```elixir
defmodule HelloWeb.Server do
import Raxx.Response
def handle_request(%{path: []}, _env) do
ok("Hello, World!")
end
def handle_request(%{path: [name]}, _env) do
ok("Hello, #{name}!")
end
def handle_request(%{path: _unknown}, _env) do
not_found()
end
end
```
Mount your server in you application. *Example using [Ace](https://github.com/CrowdHailer/Ace)*
```elixir
defmodule HelloWeb do
use Application
@raxx_app {HelloWeb.Server, []}
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
worker(Ace.TCP, [{Raxx.Adapters.Ace.Handler, @raxx_app}, [port: 8080]])
]
opts = [strategy: :one_for_one]
Supervisor.start_link(children, opts)
end
end
```
Raxx currently has adapters for three erlang servers.
- [HelloElli example](https://github.com/CrowdHailer/raxx/tree/master/example/hello_elli).
- [cowboy example](https://github.com/CrowdHailer/raxx/tree/master/example/cowboy_example).
### Principles
- Stateless HTTP request fulfill a valuable role in modern applications and will continue to do so, this simple usecase must not be compicated by catering to more advanced communication patterns.
- Use Ruby rack and Clojure ring as inspiration, 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, a RFC6265 module could be used by plug and rack
- Be a good otp citizen, work well in an umbrella app,
- Raxx is designed to be the foundation of a VC (view controller) framework. Other applications in the umbrella should act as the model.
- [Your server as a function](https://monkey.org/~marius/funsrv.pdf)
- No support for working with errors, throws, exits. We handle them in debugging because elixir is a dynamic language but they should not be used for routing or responses other than 500.
*Raxx is inspired by the [Ruby's Rack interface](http://rack.github.io/) and [Clojure's Ring interface](https://github.com/ring-clojure).*
## Raxx applications
A Raxx application module has a `handle_request/2` function that takes a `Raxx.Request` and an application environment, as arguments.
For every incomming HTTP connnection `handle_request/2` is called.
The application may indicate to the server that it should respond with a simple HTTP response buy returning a `Raxx.Response` struct.
```elixir
defmodule MySimpleApp do
def handle_request(_r, _env), do: Raxx.Response.ok()
end
```
Alternativly the the application may indicate that the connection should be upgraded.
In the case of an upgrade the returned upgrade object specifies the communication protocol required.
```elixir
defmodule MyChunkingApp do
def handle_request(_r, env), do: Raxx.Chunked.upgrade({__MODULE__, env})
def handle_info(message, _env), do: {:send, "ping"}
end
```
Currently the following upgraded protocols are supported, with others (such as websockets), in development.
- HTTP Chunked
- Server Sent Events
### Raxx.Request
`Raxx.Request`
HTTP requests to a Raxx application are encapsulated in a `Raxx.Request` struct.
```elixir
%Raxx.Request{
host: "www.example.com",
path: ["some", "path"],
...
}
```
Data can easily be read from the request directly and through pattern matching.
This allows for expressive routing in raxx apps without a routing DSL.
The hello world example is a great example of this.
The `Raxx.Request` module provides additional functionality for inspect the request.
For example inspecting cookies.
```elixir
defmodule Router do
import Raxx.Request
def handle_request(request = %{path: ["api" | rest]}, env) do
ApiRouter.handle_request(%{request | path: rest}, env)
end
def handle_request(request = %{path: ["users"], method: method}, _env) do
case method do
:GET ->
query = request.query
# Get all the users that match a query
"POST" ->
data = request.body
# Create a user with the following data
"PATCH" ->
user_id = parse_cookies(request)["user-id"]
# Update a the details of the user from a cookie session
end
end
end
```
To see the details of each request object checkout the [cowboy example](https://github.com/CrowdHailer/raxx/tree/master/example/cowboy_example).
### Raxx.Response
`Raxx.Response`
Any map with the required keys (`:status`, `:headers`, `:body`) can be interpreted by the server as a simple HTTP response.
However it is more usual to return a `Raxx.Response` struct which has sensible defaults for all fields.
Manually creating response structs can be tedious.
The `Raxx.Response` module has several helpers for creating response maps.
This include setting status codes, manipulating cookies
```elixir
defmodule FooRouter do
alias Raxx.Response
def handle_request(%{path: ["users"], method: :GET}, _env) do
Response.ok("All users: Andy, Bethany, Clive")
end
def handle_request(%{path: ["users"], method: "POST", body: data}, _env) do
case MyApp.create_user(data) do
{:ok, user} -> Response.created("New user #{user}")
{:error, :already_exists} -> Response.conflict("sorry")
{:error, :bad_params} -> Response.bad_request("sorry")
{:error, :database_fail} -> Response.bad_gateway("sorry")
{:error, _unknown} -> Response.internal_server_error("Well that's weird")
end
end
def handle_request(%{path: ["users"], method: _}, _env) do
Response.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} -> Response.ok("New user #{user}")
{:error, nil} -> Response.not_found("User unknown")
{:error, :deleted} -> Response.gone("User deleted")
end
end
def handle_request(_request, _env) do
Response.not_found("Sorry didn't get that")
end
end
```
### Raxx.Chunked
`Raxx.Chunked` allows data to be streamed to the client.
An unbounded amount of response data may be sent this way.
A Raxx application that returns a `Raxx.Chunked` struct from a call to `handle_request/2`, is indicating that it wishes to send the response in chunks.
```elixir
%Raxx.Chunked{
app: {MyHandler, :none},
...
}
```
A chunked handler must implement a `handle_info/2` callback.
This callback is called everytime the request process recieves a message, taking the message and environment as arguments.
```elixir
defmodule Ping do
def handler_request(_, _), do: Raxx.Chunked.upgrade({__MODULE__, nil})
def handle_info({:data, chunk}), do: {:send, chunk}
def handle_info({_), do: :nosend
end
```
### Raxx.ServerSentEvents
See sever sent events in examples directory.
```elixir
defmodule ServerSentEvents.Router do
alias Raxx.Response
alias Raxx.ServerSentEvents, as: SSE
def handle_request(%{path: [], method: :GET}, _opts) do
Response.ok(home_page)
end
def handle_request(%{path: ["events"], method: :GET}, env) do
Process.send_after(self, 0, 1000)
SSE.upgrade(__MODULE__, env, %{initial: "hello"})
end
def handle_request(_request, _opts) do
Response.not_found("Page not found")
end
# handle_info
def handle_info(10, _opts) do
{:send, ""}
end
def handle_info(i, _opts) when rem(i, 2) == 0 do
Process.send_after(self, i + 1, 1000)
chunk = SSE.Event.new("#{i}", event: "count") |> SSE.Event.to_chunk
{:send, chunk}
end
def handle_info(i, _opts) do
Process.send_after(self, i + 1, 1000)
:nosend
end
defp home_page do
"""
The page. see example.
"""
end
end
```
[Link to implementing server in node.js](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
[HTML living standard](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events)
<!-- ### Raxx.Test
`Raxx.Test`
This module provides helpers for testing Raxx applications.
```elixir
test "hello app says hi" do
request = Raxx.Test.get("")
response = MyApp.handle_request(request, :no_env)
assert 200 = response.status
assert "Hello, World!" = response.body
end
``` -->
## 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](www.vagrantup.com).