httpd to create a simple HTTP server without adding external dependencies
Recently I needed to add a healthcheck endpoint to an application that was solely responsbile for reading and writing to Kafka. Normally when I am creating an HTTP application I would reach for Cowboy or Phoenix, however, this use case was very simple: I just needed a single endpoint that would return
200 OK once the application was up and running and was healthy.
Before adding a new external library to our application, lets review what is given to us “for free”. It is well known that Erlang/OTP give us the tools to build robust processes, but OTP is a lot more than that. The definition from the Github page:
OTP is a set of Erlang libraries, which consists of the Erlang runtime system, a number of ready-to-use components mainly written in Erlang, and a set of design principles for Erlang programs
It is a set of libraries, the runtime, and a set of design principles. You can see the list of components here.
The library that is going to help us create our simple HTTP endpoint is called
inets. It has a few pieces to it, but the one we are interested in today is the web server, known as
httpd (it also has stuff like an FTP client and an HTTP client).
These are the steps needed to get a HTTP server working in your application
Step 1 — start
mix.exs file, add
inets to the
extra_applications value inside the
application function so that it will be started when your application starts. You probably already do this for the
Step 2 — start httpd from supervisor
In the supervisor, we need to start
httpd and supervise it. Since mine is for a healthcheck, I started it last, and used a
rest-for-one supervision strategy, so that the server would always be restarted (and be down) whenever one of the other processes fails.
Here is some of the code:
Step 3 — create Api module
Now we need to define the
Api module that we are using in the
modules option for
httpd, it will handle incoming requests to any URI, do the healthcheck or return a 404:
There is a lot talk about here. The
child_spec/1 function is really just returning the args to make the
:inets.start/2 function. You can test that seperately in
IEx by doing the following:
None of the options that we passed were optional, and you can read more about them in the
httpd documentation. The cool thing is that
httpd by default is a document server, so after you start it in
IEx you can open up your browser and look around at what is in your
/tmp folder (specified by the
Of course, we are not trying to make a document server. That is why we passed in the extra
modules option in our application code (we omitted it while testing in
IEx). There is a default list of modules that provide extra functionality for the web server, and one of those defaults is the
mod_dir which generated the Apache-style directory browser in the previous screenshot.
By passing our own list of a single module, we will remove any of the default functionality (like the directory browser) and will be responsible for implementing all of the functionality we need ourselves. You can also re-use the default modules by adding them back to your list, they are listed here. We pass in our module, where our “router” is defined further down.
Note that all of the options used
'single_quotes' and not
"double_quotes"; this is because we are dealing with an Erlang module that is expecting charlists, which are represented with the single quotes in Elixir.
There are some other uncommon things going on here to talk about.
Record is used for interfacing Erlang records, which are kind of like Elixir structs. In our case, you can see the Record that is defined in
inets which represents an incoming request to our server here. You also could achieve the same thing by doing regular pattern matching on the nested
def unquote is for defining our callback function.
httpd is expecting our module to have a
do/1 function, but we cannot define a function like that in the “regular” way because
do is already defined in Elixir! Here is an example in Erlang.
Aside from those, the code itself is pretty straight forward; we pattern match on the route, then call our healthcheck function. We then return a tuple indicating that the request may proceed (the alternative is to return a
This is just scratching the service of
httpd but it is a good demonstration of how you can check to see what is already inside of OTP before adding a new external dependency to your project.