Another way to compose "Plug"s.
An experiment to seek another way (no macro?) for composing "Plug"s. The idea is based on my experience from koajs.
This package requires OTP 21.2, which was released on Dec 12, 2018.
def deps do
[
{:kob, "~> 0.1.1"},
]
end
Docs can be found at https://hexdocs.pm/kob.
defmodule Kob.Example.Demo do
def run() do
Kob.new()
|> Kob.use(fn next ->
fn conn ->
IO.puts("start middleware 1")
conn = next.(conn)
IO.puts("finish middleware 1")
conn
end
end)
|> Kob.use(fn next ->
fn conn ->
IO.puts("start middleware 2")
conn = next.(conn)
IO.puts("finish middleware 2")
conn
end
end)
|> Kob.use(Kob.plug(Plug.Logger, log: :debug))
|> Kob.use(fn _ ->
fn conn ->
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(200, "Hello world")
end
end)
|> Kob.register_plug(MyKobPlug)
{:ok, _} = Plug.Cowboy.http(MyKobPlug, [])
end
end
It's encouraged to take a look with Kob's source code to have better understanding of it. The core part is Kob.compose
function, which is only a few lines of code.
The key design of Kob is two types:
-
@type handler :: (Plug.Conn.t() -> Plug.Conn.t())
: This is similar to Plug, which is the function to handlePlug.Conn
transformation. -
@type middleware :: (handler -> handler)
: This is how Kob does composistion. It's a higher order function to chain things together.
This design has a few benefits:
-
It enables
middleware
to determine if continue to next one. For example,fn next -> fn conn -> if for_some_case do # we want to pass `conn` to next middleware next.(conn) else # we want to response and stop pipeline conn |> Plug.Conn.send_resp(200, "OK) end end end
Plug can deal with this case via
Plug.Conn.halt
. -
It enables
middleware
to do something work afterwards. For example,fn next -> fn conn -> # pass it to next middleware and wait for its returning conn = next.(conn) # downstream middlewares are finished now. We can do some cleanup now. # For example, we can log time, we can clear session, etc. do_some_work() # pass it back to previous middleware conn end end
Plug can deal with similar case via
Plug.Conn.before_send
. -
It enables upstream
middleware
to handle errors from downstreammiddleware
. For example,Kob.compose([ # upstream middleware fn next -> fn conn -> try do next.(conn) rescue RuntimeError -> handle_error() after cleanup() end end end, # downstream middleware fn next -> fn conn -> raise "something error" end end ])
Plug can deal with similar case via
Plug.ErrorHandler
.
- We can convert a Plug to Kob middleware via
Kob.plug/2
. - We can convert a Kob struct/middleware to a Plug via
Kob.register_plug/1
.
MIT