Programming Elixir

Exploring Plug: The Body Reader

Ever wondered why sometimes Plug.Conn.read_body/2 returns nil instead of returning the raw body of the request? Today we’ll examine why this happens and how to elegantly get around it.

Plug crash course

Before exploring why this happens, let’s take a step back and take a naive look on how Plug works.

In its most basic sense, a plug is a transformation applied to a connection; a plug can be either a function, or a module that receives information about a request in the form of %Plug.Conn{} and returns a %Plug.Conn{}. A connection goes through many of these transformations throughout its lifecycle, or what we call a plug pipeline.

By design

The body can only be read once throughout a connection’s lifecycle, this is a design decision. Caching the body may lead to memory bloat, specially if dealing with large requests.

This means that if a plug reads the body at any given point in time, every subsequent plug that tries to access it, will get nil as a result instead.

Even tough there are some performance reasons to avoid caching the body, sometimes you need to access it in some of your endpoints to perform validations on the contents of the request. For this to work, we need to:

  • Identify at which stage is the body read
  • Instruct Plug to cache the body at that stage

These are high level instructions, next we’ll explore a practical example.

A practical example

In Plug based projects, Plug provides a behavior that specifies the guidelines on how to parse the request’s body, all parsers must comply with this behavior. At this point we assume that this plug reads the body and therefore no subsequent reads will be valid.

To instruct plug that we want to cache the body before parsing the body, we can pass in the body_reader option to the Parsers plug:

  plug Plug.Parsers,
      parsers: [:urlencoded, :multipart, :json],
      pass: ["*/*"],
      body_reader: {BodyReader, :cache, []},
      json_decoder: Poison

The body_reader option accepts a tuple containing a module, a function and arguments, which will get invoked before the body is parsed and discarded. In the example above we’re passing {BodyReader, :cache, []}, its implementation looks like the following:

  defmodule BodyReader do
    def cache(conn, opts)do
      {:ok, body, conn} = Plug.Conn.read_body(conn, opts)
      conn = Plug.Conn.assign(conn, :raw_body, body)
      {:ok, body, conn}

Inside BodyReader.cache/2 we are storing the body into the connection, with :raw_body as key; later on, to access it, we can do so by calling conn.assigns[:raw_body].