Extending RequestContext in akka-http for fun and profit

Marcin Rzeźnicki
Iterators
Published in
4 min readFeb 27, 2017
Knowing how things works internally, enables you to fully the potential of code you write.

Sometimes it’d be nice to put some additional information into RequestContext to have it available at all times during request processing. Ideally, one could write custom directives that access this information without any need for external parameters, just like, for example, extractHost works. I came across one such use-case when I was validating JWT tokens. I had written a custom directive that decodes the JWT token and authorizes the user:

In a nutshell, it decrypts the JWT token passed in the HTTP header into an instance of Decoded containing the usual stuff — current user’s claims and issuer, and calls the check function where it is decided whether the user is authorized to access a resource or not. In case the token cannot be decoded, it rejects the request. Simple enough.
You can now use this directive in your routing code like this:

This is readable and all, but may be costly because the token may be decrypted many times during processing. Alternatively, you could break up token decoding and authorization and write a separate Directive1[Decoded] which other directives consume. There is, of course, nothing wrong with this solution, but you have to admit that there is annoying asymmetry between akka’s built-in directives and yours, which I wanted to eliminate. The reason for this is that akka-http Route is de facto function type type Route = RequestContext ⇒ Future[RouteResult]. Akka’s Directive[L] can be seen as L => Route => Route (a route transformer + extractions) so it has access to RequestContext and can extract any property attached to the request. You cannot put your custom data into RequestContext so all you can do is extract and pass it down the route chain — unless, of course, you can extend RequestContext itself.
It looks like a hard thing to do. After all, RequestContext is baked deeply into akka-http architecture. But actually, thanks to Scala’s ability to express ad hoc polymorphism, it is rather easy. Scala’s expressiveness makes it easy to achieve things that would be barely possible in weaker languages.

So, let’s do it. First, we’re going to need a new route type. Our route will be JwtRequestContext => Future[RouteResult]

Now let’s apply ad-hoc polymorphism to model that our enhanced context is still RequestContext, implying that Route is JwtRoute (if this seems strange to you at first glance, just think of it as being that JwtRoute is a route that can use both enhanced and ‘bare’ context, while Route is restricted to using RequestContext only).

We’re ready to construct our JwtDirective type. As you remember, in akka-http Directive is very similar to (T => Route) => Route function, so in our case we need them to be (T => JwtRoute) => JwtRoute

Super. We can now write a couple of jwt-specific directives …

… but we still cannot mix them easily with akkas. Back to the drawing board. I’ve come up with the following idea: let’s introduce TopLevelJwtDirective, which ‘from the outside’ behaves like a regular Route, while ‘inside’ it is a JwtDirective where our super context can be used. Thanks to the ad hoc polymorphism work we’ve already done, it is surprisingly easy to write

As you’ve probably noticed, a top-level directive can be constructed from any akka-http directive that extracts Decoded (or Directive1[Decoded]). You can skip this step, but I find it convenient to use akka’s built-in directives within JwtDirective without going through hoops.

That’s basically it. To bootstrap everything, let’s write one top-level directive:

If you compare it with the original one, you’ll notice that it’s basically the same code, which means that you can mix directives freely (as long as Decoded is extracted).

We’re still missing things like route concatenation. But that’s easy too. We just observe that not only is Route a restricted JwtRoute, but also JwtRoute is a Route as long as there is an instance of Decoded somewhere in the scope. This instance can be then used to reconstruct JwtRequestContext from RequestContext.

We have brought all the pieces of the puzzle together.
We can write new specialized directives as easily as we’d write akka directives (we can use anything from akka as long as Decoded is implicitly defined somewhere) …

… and we can mix jwt-specific routes with our routes.

I do not feel like this is a must-have in your akka-http projects. Perhaps it’s too much magic to alleviate a minor annoyance you could have lived with. Still, it may come in handy from time to time and it is nice to assure yourself that in Scala, the sky is the limit. I’d be happy to hear your thoughts.

Thanks for reading! If you enjoyed it, hit heart button below. Would mean a lot to me and it helps other people see the story.

Check out our website iterato.rs and subscribe for more tech goodness!

--

--

Marcin Rzeźnicki
Iterators

Using Scala to build most composable and concise business logic @ iterato.rs