Kob
Another way to compose “Plug”s.
Rational
An experiment to seek another way (no macro?) for composing “Plug”s. The idea is based on my experience from koajs.
Note
This package requires OTP 21.2, which was released on Dec 12, 2018.
Installation
def deps do
[
{:kob, "~> 0.1.1"},
]
endDocs can be found at https://hexdocs.pm/kob.
Example
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
endCompare Kob and Plug
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.Conntransformation.@type middleware :: (handler -> handler): This is how Kob does composistion. It chains things together.
This design has a few benefits:
It enables
middlewareto 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 endPlug can deal with this case via
Plug.Conn.halt.It enables
middlewareto 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 endPlug can deal with similar case via
Plug.Conn.before_send.It enables upstream
middlewareto 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.
Kob and Plug are interchangeable
-
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.
License
MIT