3

Server as a Function With Kotlin — A Detailed Look at http4k

 1 year ago
source link: https://betterprogramming.pub/server-as-a-function-with-kotlin-a-detailed-look-on-http4k-22bf42055f54
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Server as a Function With Kotlin — A Detailed Look at http4k

Write your performant and lightweight HTTP server with functions only

0*Tuim6S3dBaqRVKSL

Photo by Denise Jans on Unsplash | A Swiss Army Knife — a flexible tool

Many of the server frameworks we know are heavy and opinionated, sometimes hard to debug, and not particularly effective. Luckily, there are extremely interesting alternatives. In today’s article, I want to talk to you about the concept of “Server as a Function.” We will learn about servers, lenses, clients, serverless, and how to test http4k apps.

The idea is that we write server applications based on just ordinary functions. A lightweight approach based on a concept outlined in the paper Your Server as a Function written and published by Marius Eriksen (Twitter). In the Kotlin world, the most prominent implementation of this concept is http4k. The http4k toolkit is described as an “HTTP toolset written in Kotlin with a focus on creating simple, testable APIs.” The most significant part about it is that http4k applications are just Kotlin functions that we can work with in straightforward ways.

To give you an idea, here’s a simple example.

First http4k Server Example

This code shows a fully functional http4k application consisting of a single Kotlin function app that we embedded into a SunHttp server, one example of available server implementations we can choose from. Note the type HttpHandler here, which represents one of the two essential parts of Servers as a Function:

  1. HttpHandler ((Request) -> Response): abstraction to process HTTP requests into responses by mapping the first into the latter.
  2. Filter (HttpHandler -> HttpHandler): abstraction to add pre- and post-processing like caching, debugging, authentication handling, and more to an HttpHandler. Filters are composable/stackable.

Every http4k application can be composed of HttpHandlers in combination with Filters, both of which are simple type aliases for ordinary Kotlin function types. Http4k comes with zero dependencies if we don't count the Kotlin standard library as one.

Since http4k applications, in their pure form, only entail some nested Kotlin functions; there is no reflection or annotation processing involved. As a result, http4k applications can start and stop super quickly, making them a reasonable candidate to be deployed on Function-as-a-Service environments (as opposed to, for example, Spring Boot applications, which are considered heavier).

More Advanced http4k Application

Let’s take a look at a more advanced example of an http4k server.

In this snippet, we can see a few exciting things http4k applications may entail. The first two expressions are definitions of HttpHandlers, in which the first one takes any response and maps it to an OK response containing "pong" in its body. The second handler takes a request, extracts a name, and greets the caller. In the next step, we apply routing to the handlers by assigning one particular route to each handler. As we can see, the pingPongHandler is used to serve a client who invokes /ping, while the greetHandler is mapped to/greet. Let’s go through the different aspects of this tiny server:

1. Routing

Routing in http4k works with arbitrary levels of nesting, which works flawlessly since routing itself results in a new HttpHandler (strictly speaking, a special kind of type RoutingHttpHandler), just like the original ones.

2. Filters

As mentioned before, the other important concept we want to look at is aFilter. For starters, we create a requestTimeLogger that intercepts each incoming request by measuring its processing time and logging the elapsed time. Filters can be combined using the then method, which allows us to define chains of filters. The corresponding API looks like this:

fun Filter.then(next: Filter): Filter

In the example application above, we add our custom filter to one of the default filters called GZip. Once we have combined all our filters, we want to add an HttpHandler to our filter chain. Again, there's a then function we can use to do so:

fun Filter.then(next: HttpHandler): HttpHandler

As we can see, this again results in an HttpHandler. You probably got the idea by now; it only needs two simple types to express how an HTTP server should operate.

The demonstrated GZip filter is just one of many default filters we may choose from. Others cover concerns like caching, CORS, basic authentication or cookie handling and can be found in the org.http4k.filter package.

3. Calling HttpHandlers

The final statement of the example shows how the appo is created. It itself is just an HttpHandlerwe could start as a server. But we don’t have to. The apphandler describes how requests are handled. We can use this object, as well as other separate HttpHandlers and Filters, and invoke it directly (e.g., in our tests). No HTTP is needed for this. Let's see this in action:

Http4k comes with its own implementations of a Request and a Response, first of which can be used to invoke an HttpHandler. Calling the unattractive pingPongHandler yields something similar to HTTP/1.1 200 OK pong! while calling the final app handler gives us a gzipped response due to the applied GZip filter. This call also implies a server log informing about the duration of the request: 2019-09-20T21:22:55.300768Z LOG - Request to /ping took 3ms.

Please note that, while it was fine to call pingPongHandler with a random URI (/any), we had to use the designated /ping URI when invoking the routing-backed app.

Last, but not least, we start our very own http4k HttpHandler as a server on a Jetty on port 9000. Find a list of available server implementations here.

Lenses

One of the things a sophisticated HTTP app has to deal with is taking stuff out and also putting stuff into HTTP messages. When we extract parameters from requests, we also care about validating the corresponding values. Http4k comes with a fascinating concept that helps us deal with the stated concerns: lenses.

Basic definition

Lenses, according to multiple resources, were first used in the Haskell world and are a functional concept that may appear slightly hard to understand. Let me try to describe it in an understandable manner. Let’s say we have a class Whole which comes with different fields part1, part2, and so on. A lens basically composes a getter and a setter focusing on precisely one part of Whole. A Part1Lens lens getter would take an instance of Whole to return the part it is focused on, for example, part1.

The lens setter, on the other hand, takes a Whole along with a value to set the focused part to and then returns a new Whole with the updated part. Remember that a lens can be used to both get and set a part of a whole object. What does this look like in practice?

Lenses in http4k

Following the basic idea of a lens, http4k lenses are bi-directional entities that can be used to either get or set a particular value from/onto an HTTP message. The corresponding API to describe lenses comes in the form of a DSL which also allows us to define the optionality of the HTTP part we are mounting a lens to. Since HTTP messages are a rather complex container, we can have lenses focusing on different areas of the messages: query, header, path, form field, or the body.

Let’s see some examples of how lenses can be created:

So far, the API for creating lenses looks more or less straightforward but what about using them on a target? Here’s the pseudo-code syntax for
a) Retrieving a value: <lens>.extract(<target>), or <lens>(<target>)
b) Setting a value: <lens>.inject(<value>, <target>), or <lens>(<value>, <target>)

Use Lens to retrieve value from HTTP Request

Reusing the greet sample from earlier, let's modify our code to make use of a lens to retrieve the name:

We create a bidirectional lens focusing on the query part of our message to extract a required and non-empty name from it. Now, if a client happens to call the endpoint without providing a name query parameter, the lens automatically returns an error since it was defined as "required" and “nonEmpty.”

Please note that, by default, the application exposes a lot of detail to the client announcing the error as org.http4k.lens.LensFailure: query 'name' must be string including a detailed stack trace. Rather than that, we want to map all lens errors to HTTP 400 responses to denote that the client provided invalid data. Therefore, http4k offers a ServerFilters.CatchLensFailure filter that we can easily activate in our filter chain:

Use Lens to set value in HTTP Request

After looking into extracting values from HTTP messages, how can we use the nameLens to set a value in an HTTP request?

The example shows how we create an instance of Request and inject a value via one or many lenses. We can use the Lens::inject function to specify the value we want to set into an arbitrary instance of Request. Now that we saw a basic example of a string lens, we want to dig into handling some more advanced JSON content.

JSON handling

We can choose from several JSON implementations, including the common Gson and Jackson libraries. I personally prefer Jackson as it comes with a great Kotlin module. After adding a JSON format module to our application, we can start marshaling objects to and from HTTP messages using lenses.

Let’s consider a partially complete REST API that manages persons:

In this example, we see a class that provides two handlers representing common actions you would expect from a REST API. The getAllHandler fetches all stored entities and returns them to the client. We make use of a BiDiBodyLens<List<Person>> (BiDirectional) that we created via the org.http4k.format.Jackson.auto extension for Jackson.

As noted in the http4k documentation, the auto() method needs to be manually imported as IntelliJ won't pick it up automatically. We can use the resulting lens like already shown earlier by providing a value of type List<Person> and inject it into an HTTP Response as shown in the getAllHandler implementation.

The postHandler, on the other hand, provides an implementation of an HttpHandler, that extracts a Person entity from the request and adds it to the storage. Again, we use a lens to extract that JSON entity from the request easily.

This already concludes our sneak peek on lenses. As we saw, lenses are a fantastic tool that lets us extract and inject parts of an HTTP message and also provides simple means of validating those parts. Now, that we have seen the most fundamental concepts of the http4k toolset, let’s consider how we can test such applications.

Testing

Most of the time, when we consider testing applications that sit on top of a web framework, we have to worry about the details of that framework which can make testing harder than it should be.

Spoiler alert: This is not quite the case with http4k🎉.

We have already learned that HttpHandlers, one of the two core concepts in the http4k toolset, are just regular Kotlin functions mapping requests to responses and even a complete http4k application again is just an HttpHandler and thus a callable function. As a result, entire and partial http4k apps can be tested easily and without additional work.

Nevertheless, the makers of http4k thought that it would still be helpful to provide some additional modules to support us with testing our applications. One of these modules is http4k-testing-hamkrest, which adds a set of Hamkrest matchers, we can use to verify details of message objects more easily.

Http4k Handler Test Example

This snippet demonstrates a test for the PersonHandlerProvider we have worked with earlier already. As shown, it's pretty straightforward to call an HttpHandler with a Request object and then use Hamkrest or whatever assertion library you prefer to check the resulting Response. Testing Filters, on the other hand, is slightly harder.

To be honest, though, it’s just one tiny thing we need to do on top of what we did with handlers. Filters map one HttpHandler into another one by applying some intermediate pre- or post-processing. Instead of investigating the mapping between handlers itself, it would be more convenient to again send a Request through that filter and look into the resulting Response. The good news is that it's super easy to do just that.

Http4k Filter Test Example

We have a Filter called addExtraHeaderFilter that adds a custom header to a processed request and then forwards it to the next filter. The goal is to send a simple request through that filter in our test. What we can do is make the filter a simple HttpHandler by adding a dumb { Response(OK) } handler to it via then. As a result, we can invoke the newly created handler, now containing our very own filter, and investigate whether the resulting Response object contains the new expected header. There we go — both handlers and filters got tested.

To wrap up, I want to say that this was just a quick look at the happy paths of testing http4k apps with mostly familiar tools. It might become necessary to test against the actual running server and verify responses on a lower level, i.e. compare the resulting JSON. Doing that is also possible and supported via the Approval Testing module. Later in this article, we want to look at the client module of http4k, which again opens up some new possibilities.

Serverless

One of the hottest topics of our time is Serverless computing. You know, that thing where we can run our code on other people’s servers. One part of it is known as Function as a Service (FaaS). The most common FaaS platforms include AWS Lambda, Google Cloud Functions, and Microsoft Azure Functions. The general idea is that these vendors provide a platform where we can deploy our code, and they take care of managing resources and scaling our application on demand.

One of the tricky parts of Serverless is that our functions may be spun down by the platform if they are not being used for a while. This requires a fresh startup at the point somebody needs them again. What does that mean for us? We need to choose target platforms and tools that allow a fast start-up of our application. Spring on the JVM in its classical form, for instance, would probably not be the best tool for that use case. However, as you can imagine, http4k with its small footprint and super quick start-up times is a great choice. It even comes with native support for AWS Lambda.

Client as a Function

By now, we have learned how cool http4k is and why it’s a great tool to develop server applications. HTTP servers don’t make much sense without clients using them, so we want to conclude this article by looking at the other side — Clients as a Function.

The http4k core library comes with everything we need to get started with clients. Clients in http4k again are just a special form of an HttpHandler, as we can see in this little snippet:

The JavaHttpClient used here is the default implementation that comes with the core library. If we were to prefer OkHttp, Apache, or Jetty instead, we would use a related module to replace the default. Since we program against interfaces (clients are HttpHandlers), it's not a big deal to swap out implementations at any time. The core library obviously comes with several default Filters we can apply to our client. Those are found in the ClientFilters.kt file that contains stuff like BasicAuth, Gzip and more stuff you'd expect.

The fact that all concepts of http4k servers — including handlers, filters, and lenses — can be reused in http4k clients, opens up quite a few possibilities. It can make much sense to, for example, use the client module to test your servers and vice versa. I fell in love with the entire stack quite quickly and have used it in production for a couple of years now.

Summary and Lookout

I personally learned to appreciate http4k a lot in the last couple of years. Once you’ve made yourself comfortable with the basic concepts, it becomes straightforward to develop server applications quickly. Http4k comes with an incredible list of supported concepts and technologies including OAuth, Swagger, Websockets, XML, and many, many more.

Its modular nature allows us to add functionality by applying dependencies as needed, and due to its simple base types, it is highly extensible. Http4k is a toolset that allows us to write applications with quick startup times and thereby also makes it a valid alternative when it comes to FaaS and Serverless computing.

As if this wasn’t enough, the toolset also includes sophisticated means for writing HTTP clients, which we learned about in the last section. Overall, http4k is a promising technology that you should definitely consider when choosing your next HTTP toolset.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK