The long version:
It would be more accurate to say that a Plug
involves some kind of function that satisfies the (Plug.Conn.t, Plug.opts) :: Plug.Conn.t
type signature.
The documentation is also clear that:
From what I can tell the 2nd parameter on (Plug.Conn.t, Plug.opts) :: Plug.Conn.t
is useless (***) with a function plug - it exists only for compatibility with the module plug.
The documentation’s example module plug looks like this:
defmodule MyPlug do
import Plug.Conn
def init(options) do
# initialize options
options
end
def call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello world")
end
end
There are two functions call/2
and init/1
. Again in this particular case the 2nd parameter _opts
for call/2
is unused. While init/1
and call/2
exist in the same module there is a difference that is easy to miss:
i.e.
init/1
is called during compile time to generate a “compile time configuration” for thePlug
call/2
is called during run time (while the request is being processed) and is passed the options that were generated byinit/1
during compilation.
The compilation/runtime split is sometimes used for testing but init/1
gives a module plug a standardized method for compile time configuration (or think of it as server configuration rather than request configuration).
Plug.Cowboy
makes use of the Plug
specification.
init/1
is used when the dispatch is created during compile time (or more accurately when the server is starting - but not yet processing any requests):
call/2
is used at runtime in the Plug.Cowboy.Handler
:
If that so why router is being called a plug?
There is a lot of macro (compile time) activity in hello_web/router.ex
:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
end
First of all use HelloWeb, :router
injects a lot of code into HelloWeb.Router
. Looking into HelloWeb.ex
there is
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
So use HelloWeb, :router
turns into Kernel.apply(HelloWeb, :router)
(see apply/3
) at compile time which is equivalent to HelloWeb.router()
. Looking up into the HelloWeb
module you’ll find that router
function:
def router do
quote do
use Phoenix.Router
import Plug.Conn
import Phoenix.Controller
end
end
So use HelloWeb, :router
turns into:
use Phoenix.Router
import Plug.Conn
import Phoenix.Controller
The same type of thing happens recursively with use Phoenix.Router
- in deps/phoenix/lib/phoenix/router.ex
:
defmacro __using__(_) do
quote do
unquote(prelude())
unquote(defs())
unquote(match_dispatch())
end
end
The interesting one is match_dispatch()
:
defp match_dispatch() do
quote location: :keep do
@behaviour Plug
@doc """
Callback required by Plug that initializes the router
for serving web requests.
"""
def init(opts) do
opts
end
@doc """
Callback invoked by Plug on every request.
"""
def call(conn, _opts) do
%{method: method, path_info: path_info, host: host} = conn = prepare(conn)
case __match_route__(method, Enum.map(path_info, &URI.decode/1), host) do
:error -> raise NoRouteError, conn: conn, router: __MODULE__
match -> Phoenix.Router.__call__(conn, match)
end
end
defoverridable [init: 1, call: 2]
end
end
This is the function that turns your hello_web/router.ex
into a module plug as it provides the compile time init/1
function and the runtime call/2
function.
Edit: *** Correction - not entirely useless. For example with Plug.Builder.plug/2
you can supply the options to be passed to a function plug.