elixirphoenix-frameworkplug

How do you run middleware functions post response in Phoenix framework?


I'm developing a simple website in Elixir with Phoenix. I'd like to add some custom middleware that runs after a response has been generated. For example, in order to log the total number of bytes in each response I'd like to have a Plug like this

defmodule HelloWeb.Plugs.ByteLogger do
  import Plug.Conn
  require Logger

  def init(default), do: default

  def call(conn, default) do
    log("bytes sent: #{String.length(conn.resp_body)}")
  end
end

Trying to use this plug in one of the Phoenix pipelines in the router won't work though, they are all run before the response is rendered. Instead it causes a FunctionClauseError since conn.resp_body is nil. I'm not sure how to use this plug so it can run after the response is rendered.


Solution

  • I think you are looking for register_before_send/2.

    This allows to register callbacks that will be called before resp_body gets set to nil as explained here.

    Should look like:

    defmodule HelloWeb.Plugs.ByteLogger do
      import Plug.Conn
      require Logger
    
      def init(default), do: default
    
      def call(conn, default) do
        register_before_send(conn, fn conn ->
          log("bytes sent: #{String.length(conn.resp_body)}")
    
          conn
        end)
      end
    end
    

    Edit: I don't think you should be using String.length for the byte size:

    The following could do the job, but with a significant performance impact due to the need of concatenating the body:

    conn.resp_body |> to_string() |> byte_size()
    

    :erlang.iolist_size/1 seems to work well and I suppose is much better performance-wise.