tokumei v0.6.5 Tokumei.Router
Route incoming HTTP requests by path and method.
Provides route/2/3/4
to direct requests to handlers
defmodule MyApp.Router
use Tokumei.Router
# Any value can be returned by a route.
# import helpers from Raxx.Response to return valid response to the Raxx.Server
import Raxx.Response
# route_name is optional
# Routes are created in blocks grouped, first by path...
@route_name :users
route ["users"], request, _config do
# ...and second by request method.
:GET ->
ok("Dan, Lucy, Jane")
:POST ->
created("Added #{request.body}")
end
# If access to the request and config is not needed use [`route/2`](#route/2)
route ["about"] do
:GET ->
ok("All about Tokumei.Router")
end
# paths may contain any number of variables
@route_name :users_cart
route ["users", user_id, "carts", cart_id], request do
:GET ->
ok("This is cart: '#{cart_id}' for user: '#{user_id}'")
end
# Application configuration is optional third argument.
@route_name :forgot_password
route ["users", "forgot-password"], _request, %{mailer: mailer} do
:POST ->
mailer.send_password_reset(request.body)
ok("Reset sent")
end
# Tokumei.Router is implemented as middleware.
# A fallback `handle_request/2` must implemented.
# This can be handled by earlier middleware, e.g. Tokumei.NotFound
def handle_request(request, _) do
# Checkout Tokumei.ErrorHandler for consolidating managing error return values.
{:error, {:not_found, request}}
end
end
The router will be a Raxx application, it can be invoked directly. For example for testing
request = Raxx.Request.get("/users")
response = MyApp.Router.handle_request(request, :some_config)
assert %{body: "Dan, Lucy, Jane", status: 200} = response
request = Raxx.Request.post("/users", "Bill")
response = MyApp.Router.handle_request(request, :some_config)
assert %{body: "Added Bill", status: 201} = response
It can be mounted on a Raxx compatible server
{:ok, pid} = Ace.HTTP.start_link({MyApp.Router, :some_config}, port: 8080)
NOTE: Tokumei.Router does not provide any guarantees on return value.
It can be used in conjunction with other middleware to ensure responses can be sent to client,
e.g. Tokumei.ErrorHandler
, Tokumei.ContentLength
etc.
The router will have helpers for generating paths from route names.
assert "/users" == MyApp.Router.path_to(:users)
assert "/users/jill" == MyApp.Router.path_to(:user, ["jill"])
The router will have helpers for generating fully qualified urls from route names.
request = %Raxx.Request{host: "www.example.com", scheme: "http", mount: ["api"]}
assert "http://www.example.com/api/users" == url_to(request, :users)
assert "http://www.example.com/api/users/jill" == url_to(request, :user, ["jill"])
Extensions
Tokumei.Router
helps create Raxx Handlers, with the aim of improving readability.
The DSL is deliberatly minimal, to maintain the underlying semantics.
However the following extensions might be useful,
and can be added if enough usecases are found.
[x] Don’t create raxx_path for unnamed routes
[ ] Automatically add the 405 Method Not Implemented clause by inspecting matches
[x] Being able to fetch a list of all the routes implemented.
- simplest is to return all route names, MyRouter.routes() -> [:users, :user, :root]
useful to return representation of paths, but have to be built from match
- :user “[“users”, id]” # built using Macro.to_string
- :user “/users/:id” # requires deconstruction of match
useful to list all implemented methods, tricky as methods can be match in serveral non-trivial ways
- :GET -> # action
- m when in [:POST, :PATCH] -> # action
- because list of known methods is small we could execute the action clause finding out the known methods
[ ] Being able to list all the routes from a mix task
"/", [GET] :users "/users", [GET, POST] :user "/users/:id", [GET, PATCH, DELETE]
[ ] mount functionality
mount "/api", {request, config} do # request is using mount value # To controller ApiRouter.handle_request(request, config) # To service request = %{request | mount: [], host: "api.dmz"} # De militarized zone, i.e. private make_request(request) end
[ ] Direct routing to controllers
```elixir @route_name :home route "/", GET: HomePage, POST: SendEnquiry @route_name :home route "/", {request, config} do :GET -> HomePage.handle_request(request, config), :POST -> SendEnquiry.handle_request(request, config) end ```
- Simplifies knowing about methods implemented if setup as kwlist not match
- Perhaps belongs as separate dispatcher (or similar named module)
[x] handle situation where a super value is not defined
Module.defines?(__MODULE__, {:handle_request, 2})
[ ] Gateway functionality for direct forwarding to {ip, port}, hashtag microservices
[ ] Handle globbing eg
["stuff" | rest]
. Though maybe this can be solved with mount. I have never used variables and globs in the same route.
Summary
Functions
Implements route with request and config unmatched.
See route/4
.
Implements route with config unmatched.
See route/4
.