An Elixir Plug that targets a specific path

Jean-Hadrien Chabran
Inside Heetch
Published in
3 min readMar 29, 2018

Plug is a cornerstone of Elixir and handling HTTP requests. It’s striking how simple it is to write one:

Yet simple does not necessarily mean that it is easy to use, especially when you’re getting started.

For example, if you look at the code above, it’s not obvious how to update it so it only acts on a specific path. That’s precisely what we’ll be looking at here.

A concrete example

An example of such a feature could be a health check handler. It’s an HTTP route (ex: /_health_check) that can be requested to know if a given web application is performing correctly or not.

  • A 200 ok status code means that yes, it’s operating properly.
  • Anything else would mean that there is a problem and this instance of our application should not receive requests anymore.

This way clients have a way to know if it’s running smoothly or not. For example, container orchestration solutions such as Kubernetes and Mesosphere DC/OS use this mechanism to know if it should kill Docker container running the app or not.

We will be using this use case during the rest of this post.

Why not just use Plug.Router

If all we want is to match on a given path and respond with a specific response, Plug.Router sounds like an immediate solution. But what if we need to share the health check code among Phoenix apps and other Plug-based apps ?

A module using Plug.Router is a plug itself and thus could be shared. But can’t we just write a bare plug instead for the sake of simplicity?

Let’s see where we start if we’re not using Plug.Router to match specific routes. For a module to be a plug, it needs the call/2 callback to be implemented.

call/2 takes two parameters, conn which is a %Plug.Conn{}, representing our connection to the client and opts, a set of options.

Looking at what is inside the %Plug.Conn{} struct, we can see that there are many interesting fields:

All these attributes being inside conn, we can pattern match against them. request_path is a good fit but it may contain trailing slashes. Instead, path_info is a list of each segment in the path, so we don’t have to bother with slashes at all, making it a better choice here.

Let’s see how it goes:

So, in concrete terms, given that the health check path is /_health_check:

  • If path_infomatches, respond with a 200status code and halt the plug pipeline so that no other plugs will be called.
  • Otherwise, just pass the request down the chain to other plugs, like plug :match from Plug.Router or Phoenix.Router.

Using our plug with Plug.Router

The HealthCheck.Plug can be used like any other plug but, if we want to use it within a Plug.Router, where it is inserted can change the outcome. Inserting it after plug :dispatchand plug :matchwill lead to the request being caught by the catch-all route before reaching our plug.

So the right way to use it is as follows:

Using our plug with Phoenix

Phoenix works similarly. The plug needs to be inserted before any routing happens. Without diving into details, Phoenix implements its HTTP handler in two components, the endpoint which is a top-level plug and the router, where routes are defined.

So here, it’s required to insert our Plug in the endpoint, before inserting the router (exactly like we’ve seen previously).

Conclusion

All that was done in here was to inspect what’s inside a connand pattern match against it. Very often, pattern matching is itself powerful enough to solve most problems.

In the end and like in other functional languages, looking at what structures are being carried around is really helpful and provides a lot of insight. It should be one of the first things to inspect when exploring code.

--

--