Question about Plug from the Programming Phoenix book

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 the Plug
  • call/2 is called during run time (while the request is being processed) and is passed the options that were generated by init/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.

17 Likes