In a recent project, I had to implement idempotent API endpoints in a Phoenix application. And as a part of this, I had to store the MD5 hash of each request body along with its idempotency key and other request data. And the latest Plug makes this easier thanks to this PR.
So, let us take a step back and see what is required. We want to plug into the
request pipeline, read the body, compute its md5 hash and store it in a private
variable on the connection. Before Plug
1.5.1,
if you wanted to read the requested body you had to duplicate your request
parsers as they directly read from the connection using Plug.Conn.read_body
which read the content, parsed it and discarded it. If you ended up putting
your plug before the parsers and read the body, the JSON parser would fail to
see any request data as the request would already have been read.
To work around this limitation a new option called body_reader
was added to
the Plug.Parsers
plug. This allows you to get in the middle of the parser and
the connection and have custom code which can read the request body, cache it
and hand over the request body to the parsers which ends up in a clean
implementation. So, let us take a look at the code that is required to add MD5
hashing to our requests.
The first thing we need is a custom request body reader which can be used by our parsers. From the example in the Plug documentation, it can be as simple as the code below:
1 | defmodule BodyReader do |
The above module has a read_body
function which takes a connection, reads
the request body, computes its md5 hash and shoves it into a private key on the
connection and returns the body and connection to the parsers to pass them
along.
Once we have this custom reader, we just need to configure our parsers to use this by changing our endpoint to have the following code:
1 | plug Plug.Parsers, |
Once you set this up your actions can access the md5sum of a request using
conn.private[:md5sum]
. And that is all you need to compute the MD5 sum of your
requests.
The same technique can be used to authenticate webhook requests from GitHub, Dropbox and other services which sign their requests with an HMAC key.