Directives

A “Directive” is a small building block used for creating arbitrarily complex route structures. Akka HTTP already pre-defines a large number of directives and you can easily construct your own:

Basics

Directives create Routes. To understand how directives work it is helpful to contrast them with the “primitive” way of creating routes.

Routes effectively are simply highly specialised functions that take a RequestContext and eventually complete it, which could (and often should) happen asynchronously.

The complete directive simply completes the request with a response:

Scala
val route = complete("yeah")
Java
Route route = complete("yeah");

Writing multiple routes that are tried as alternatives (in-order of definition), is as simple as using the concat(route1, route2), method:

Route routes = concat(
  pathSingleSlash(() ->
    getFromResource("web/calculator.html")
  ),
  path("hello", () -> complete("World!))
);

You could also simply define a “catch all” completion by providing it as the last route to attempt to match. In the example below we use the get() (one of the MethodDirectives) to match all incoming GET requests for that route, and all other requests will be routed towards the other “catch all” route, that completes the route:

Route route =
  get(
    () -> complete("Received GET")
  ).orElse(
    () -> complete("Received something else")
  )

If no route matches a given request, a default 404 Not Found response will be returned as response.

Structure

The general anatomy of a directive is as follows:

Scala
name(arguments) { extractions =>
  ... // inner route
}
Java
directiveName(arguments [, ...], (extractions [, ...]) -> {
  ... // inner route
})

It has a name, zero or more arguments and optionally an inner route (The RouteDirectives are special in that they are always used at the leaf-level and as such cannot have inner routes).

Additionally directives can “extract” a number of values and make them available to their inner routes as function arguments. When seen “from the outside” a directive with its inner route form an expression of type Route.

What Directives do

A directive can do one or more of the following:

  • Transform the incoming RequestContext before passing it on to its inner route (i.e. modify the request)
  • Filter the RequestContext according to some logic, i.e. only pass on certain requests and reject others
  • Extract values from the RequestContext and make them available to its inner route as “extractions”
  • Chain some logic into the RouteResult future transformation chain (i.e. modify the response or rejection)
  • Complete the request

This means a Directive completely wraps the functionality of its inner route and can apply arbitrarily complex transformations, both (or either) on the request and on the response side.

Composing Directives

As you have seen from the examples presented so far the “normal” way of composing directives is nesting. Let’s take a look at this concrete example:

Scala
sourceval route: Route =
  path("order" / IntNumber) { id =>
    concat(
      get {
        complete {
          "Received GET request for order " + id
        }
      },
      put {
        complete {
          "Received PUT request for order " + id
        }
      }
    )
  }
Java
sourceimport static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.path;
import static akka.http.javadsl.server.Directives.put;

import static akka.http.javadsl.server.PathMatchers.integerSegment;
import static akka.http.javadsl.server.PathMatchers.segment;

Route orElse() {
  return path(segment("order").slash(integerSegment()), id ->
    get(() -> complete("Received GET request for order " + id))
      .orElse(
        put(() -> complete("Received PUT request for order " + id)))
  );
}

Here the get and put directives are chained together using the orElse method to form a higher-level route that serves as the inner route of the path directive. Let’s rewrite it in the following way:

Scala
sourcedef innerRoute(id: Int): Route =
  concat(
    get {
      complete {
        "Received GET request for order " + id
      }
    },
    put {
      complete {
        "Received PUT request for order " + id
      }
    }
  )

val route: Route = path("order" / IntNumber) { id => innerRoute(id) }
Java
sourceimport static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.extractMethod;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.path;
import static akka.http.javadsl.server.Directives.put;
import static akka.http.javadsl.server.PathMatchers.integerSegment;
import static akka.http.javadsl.server.PathMatchers.segment;

Route getOrPut(Supplier<Route> inner) {
  return get(inner)
    .orElse(put(inner));
}

Route customDirective() {
  return path(segment("order").slash(integerSegment()), id ->
    getOrPut(() ->
      extractMethod(method -> complete("Received " + method + " for order " + id)))
  );
}

In this previous example, we combined the get and put directives into one composed directive and extracted it to its own method, which could be reused anywhere else in our code.

Instead of extracting the composed directives to its own method, we can also use the available anyOf combinator. The following code is equivalent to the previous one:

Scala
sourceval route =
  path("order" / IntNumber) { id =>
    (get | put) { ctx =>
      ctx.complete(s"Received ${ctx.request.method.name} request for order $id")
    }
  }
Java
sourceimport akka.http.javadsl.server.Directives;

import static akka.http.javadsl.server.Directives.anyOf;

import static akka.http.javadsl.server.PathMatchers.integerSegment;
import static akka.http.javadsl.server.PathMatchers.segment;

Route usingAnyOf() {
  return path(segment("order").slash(integerSegment()), id ->
    anyOf(Directives::get, Directives::put, () ->
      extractMethod(method -> complete("Received " + method + " for order " + id)))
  );
}

The previous example, tries to complete the route first with a GET or with a PUT if the first one was rejected.

In case you are constantly nesting the same directives several times in you code, you could factor them out in their own method and use it everywhere:

sourceimport static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.extractClientIP;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.path;

Route getWithIP(Function<RemoteAddress, Route> inner) {
  return get(() ->
      extractClientIP(address -> inner.apply(address))
  );
}

Route complexRoute() {
  return path(segment("order").slash(integerSegment()), id ->
    getWithIP(address ->
      complete("Received request for order " + id + " from IP " + address))
  );
}

Here we simple created our own combined directive that accepts GET requests, then extracts the method and completes it with an inner route that takes this HTTP method as a parameter.

Again, instead of extracting own combined directives to its own method, we can make use of the allOf combinator. The following code is equivalent to the previous one:

sourceRoute complexRouteUsingAllOf() {
  return path(segment("order").slash(integerSegment()), id ->
    allOf(Directives::get, Directives::extractClientIP, address ->
      complete("Received request for order " + id + " from IP " + address))
  );
}

In this previous example, the the inner route function provided to allOf will be called when the request is a GET and with the extracted client IP obtained from the second directive.

As you have already seen in the previous section, you can also use the concat method defined in RouteDirectives as an alternative to orElse chaining. Here you can see the first example again, rewritten using concat:

sourceimport static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.path;
import static akka.http.javadsl.server.Directives.put;
import static akka.http.javadsl.server.PathMatchers.integerSegment;
import static akka.http.javadsl.server.PathMatchers.segment;

Route usingConcat() {
  return path(segment("order").slash(integerSegment()), id ->
    concat(get(() -> complete("Received GET request for order " + id)),
          put(() -> complete("Received PUT request for order " + id)))
  );
}

The concat combinator comes handy when you want to avoid nesting. Here you can see an illustrative example:

sourceimport static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.head;
import static akka.http.javadsl.server.Directives.path;
import static akka.http.javadsl.server.Directives.put;
import static akka.http.javadsl.server.PathMatchers.integerSegment;
import static akka.http.javadsl.server.PathMatchers.segment;

Route multipleRoutes() {
  return path(segment("order").slash(integerSegment()), id ->
    concat(get(()  -> complete("Received GET request for order " + id)),
          put(()  -> complete("Received PUT request for order " + id)),
          head(() -> complete("Received HEAD request for order " + id)))
  );
}

Notice how you could adjust the indentation in these last two examples to have a more readable code.

Note that going too far with “compressing” several directives into a single one probably doesn’t result in the most readable and therefore maintainable routing code. It might even be that the very first of this series of examples is in fact the most readable one.

Still, the purpose of the exercise presented here is to show you how flexible directives can be and how you can use their power to define your web service behavior at the level of abstraction that is right for your application.

Type Safety of Directives

When you combine directives with the anyOf and allOf methods the routing DSL makes sure that all extractions work as expected and logical constraints are enforced at compile-time.

For example you cannot anyOf a directive producing an extraction with one that doesn’t:

Scala
val route = path("order" / IntNumber) | get // doesn't compile
Java
anyOf(this::get, this::extractClientIP, routeProvider) // doesn't compile

Also the number of extractions and their types have to match up:

Scala
val route = path("order" / IntNumber) | path("order" / DoubleNumber)   // doesn't compile
val route = path("order" / IntNumber) | parameter("order".as[Int])     // ok
Java
anyOf(this::extractClientIP, this::extractMethod, routeProvider) // doesn't compile
anyOf(bindParameter(this::parameter, "foo"), bindParameter(this::parameter, "bar"), routeProvider) // ok

In this previous example we make use of the bindParameter function located in akka-http/akka.http.javadsl.common.PartialApplication. In order to be able to call anyOf, we need to convert our directive that takes 2 parameters to a function that takes only 1. In this particular case we want to use the parameter directive that takes a String and a function from String to Route, so to be able to use it in combination with anyOf, we need to bind the first parameter to foo and to bar in the second one. bindParameter(this::parameter, "foo") is equivalent to define your own function like this:

Route parameterFoo(Function<String, Route> inner) {
  return parameter("foo", inner);
}

When you combine directives producing extractions with the allOf method all extractions will be properly gathered up:

Scala
val order = path("order" / IntNumber) & parameters("oem", "expired".optional)
val route =
  order { (orderId, oem, expired) =>
...
  }
Java
allOf(this::extractScheme, this::extractMethod, (scheme, method) -> ...) 

Directives offer a great way of constructing your web service logic from small building blocks in a plug and play fashion while maintaining DRYness and full type-safety. If the large range of Predefined Directives does not fully satisfy your needs you can also easily create Custom Directives.

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.