Hello Http4k
This is a quick intro into http4k — a Kotlin library for writing HTTP servers and clients. Unlike many other libraries and frameworks with (over)complicated core abstractions and workflows, http4k uses a few simple concepts which encourage good design and testability.
The examples below are based on the “Live coding: Server as a function with http4k” talk I’ve been doing earlier this year.
Hello handler
The main concept in http4k is HttpHandler
, which is literally a function
that takes immutable Request
and returns immutable Response
(where Request
and Response
support all the usual HTTP things like headers and queries):
So the simplest server in http4k is just a lambda which returns an instance of Response
with OK
status and (optional) body content. Of course, lambdas can’t send/receive HTTP requests over the network — for that we need an actual HTTP server.
Http4k doesn’t attempt to reinvent the wheel and instead of implementing its own server/client, it has adapters to integrate
with existing HTTP Java servers/clients.
In this case, let’s use ApacheServer
which is an adapter for Apache HttpComponents:
We can run the code above in IDE and test it from command line with curl:
The output looks good but it would be nice to use server code directly from Kotlin.
To do this we need an HTTP client. In particular, we can use OkHttp
which is an adapter for OkHttp library.
Note that HTTP client is also HttpHandler
, i.e. it has the same type as the server!
If we run the code above, the output will be almost identical to the curl output:
Since both server and client have the same signature, we can refactor the code
to remove ApacheServer
and OkHttp
adapters so that the server handler is called directly:
And after a couple more refactorings:
In general, this outlines the http4k approach for making HTTP servers testable: start the servers in the same process and use in-memory communication, or if it’s an external server, replace it with a fake server (supported by contract tests) or with record/replay client.
Hello routes
The server above works just fine except that it replies to any request regardless of the URI. For example, it will reply with “Hello 🌍” even if we POST to http://localhost:1234/foo
.
In order to fix this, we can use the routes()
function which takes one or more RoutingHttpHandler
s and aggregates them into a composite RoutingHttpHandler
.
The simplest way to create a RoutingHttpHandler
is by using the following mini-DSL. First, "/hello" bind GET
bundles path and HTTP method into the PathMethod
data class, and then, ... to { request: Request -> ... }
creates RoutingHttpHandler
.
If we run the code above, it will print 404
response from RoutingHttpHandler
because httpClient
still requests “/” while httpHandler
only responds to the “/hello” path. Obviously, to fix this we should modify the request:
Hello filter
Another important concept in http4k is Filter
, which is essentially an HttpHandler
wrapper:
To illustrate this, let’s create a Filter
from scratch. The simplest possible Filter
is the one that just returns its argument (note in the code below the usage of the Filter { ... }
function which saves us the effort of creating an anonymous object):
Let’s wrap nextHandler
into another HttpHandler
called wrapperHandler
.
Functionally, the code still works in the same way (and doesn’t do anything useful):
However, we can now add more functionality to the wrapperHandler
. For example, to create “catch all” filter we can surround nextHandler(request)
with try/catch expression and return I_M_A_TEAPOT status in case of Exception:
To try out the filter, let’s make the server extract the “name” query parameter from the request and throw an exception if the “name” parameter is missing: request.query("name") ?: error("No name")
. We can apply the filter using .withFilter(catchAll)
:
As expected, it’s a 200
response on “hello?name=world” GET request:
And it’s a 418
“I’m a teapot” response if we forget to specify the “name”:
You can find more filters bundled with http4k in org.http4k.filter.ServerFilters
and org.http4k.filter.ClientFilters
objects.
HTTP as a function
Just like any other library or framework http4k is only an abstraction on top of the HTTP protocol. Even the simplest GET request can end up talking to multiple servers (think DNS, SSL) and with containerised clouds (using kubernetes, etc.) there is even more stuff going on in the background. So the library design is by no means a representation of reality but rather it’s an abstraction for the library users. Arguably, for the majority of users plain old function (POF?) with an immutable request/response is the best abstraction over HTTP.
The design of http4k was inspired by “Your Server as a Function” paper which defines HttpHandler
in a bit more complicated way and roughly translates to Kotlin like this:
interface HttpHandler<Request, Response>: (Request) -> Future<Response>
The main difference compared to http4k is the Future
in the return type which highlights the asynchronous nature of HTTP requests. The advantage is that requests can be treated as tasks to be scheduled for execution asynchronously. The disadvantage is that Future
complicates every request and response. There are potential performance benefits, and no doubt some large-scale companies can benefit from this, but the chances are you’re not one of them. Another argument against using Future
is that HTTP abstraction should only be dealing with HTTP and stay away from threading strategy, similar to how you would avoid mixing HTTP and domain logic.
From the simple design point of view, it’s also not easy to justify the conceptual complexity of an average HTTP framework with its own lifecycle and special testing suite. That’s not to mention the use of annotations, which are pretty much custom keywords and should be used sparingly.
Summary
One thing to remember from this blog, is that HTTP servers and clients are uniform, i.e. both server and client have the same type (in Kotlin terms (Request) -> Response
or variation of it). This is exactly what makes it possible to write “your server as a function” and http4k takes this approach to its simplest form. So if you’re thinking about writing a new web-framework or library, please really consider using this as its core concept!
It’s worth mentioning that http4k has been successfully used in large-scale enterprise systems. There are quite a few http4k modules and this blog just scratches the surface of the core module. You can find out more about http4k on its website. For a bit more in-depth overview, here is a video by http4k authors:
Given the amount of web-frameworks and technical marketing (things that come to mind are Spring and, obviously, this blogpost), it can be genuinely hard to know what is the right tool for the problem at hand. The important thing is to avoid Resume Driven Development by actually solving the problem instead of focusing on a particular technology.