Routing DSL Overview

Routing DSL Overview

The Akka HTTP Low-Level Server-Side API provides a Flow- or Function-level interface that allows an application to respond to incoming HTTP requests by simply mapping requests to responses (excerpt from Low-level server side example):

final Function<HttpRequest, HttpResponse> requestHandler =
  new Function<HttpRequest, HttpResponse>() {
    private final HttpResponse NOT_FOUND =
      HttpResponse.create()
        .withStatus(404)
        .withEntity("Unknown resource!");


    @Override
    public HttpResponse apply(HttpRequest request) throws Exception {
      Uri uri = request.getUri();
      if (request.method() == HttpMethods.GET) {
        if (uri.path().equals("/")) {
          return
            HttpResponse.create()
              .withEntity(ContentTypes.TEXT_HTML_UTF8,
                "<html><body>Hello world!</body></html>");
        } else if (uri.path().equals("/hello")) {
          String name = uri.query().get("name").orElse("Mister X");

          return
            HttpResponse.create()
              .withEntity("Hello " + name + "!");
        } else if (uri.path().equals("/ping")) {
          return HttpResponse.create().withEntity("PONG!");
        } else {
          return NOT_FOUND;
        }
      } else {
        return NOT_FOUND;
      }
    }
  };

While it'd be perfectly possible to define a complete REST API service purely by inspecting the incoming HttpRequest this approach becomes somewhat unwieldy for larger services due to the amount of syntax "ceremony" required. Also, it doesn't help in keeping your service definition as DRY as you might like.

As an alternative Akka HTTP provides a flexible DSL for expressing your service behavior as a structure of composable elements (called Directives) in a concise and readable way. Directives are assembled into a so called route structure which, at its top-level, can be used to create a handler Flow (or, alternatively, an async handler function) that can be directly supplied to a bind call.

Here's the complete example rewritten using the composable high-level API:

import akka.NotUsed;
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.http.javadsl.Http;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.model.HttpEntities;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.Route;
import akka.stream.ActorMaterializer;
import akka.stream.javadsl.Flow;

import java.io.IOException;
import java.util.concurrent.CompletionStage;

public class HighLevelServerExample extends AllDirectives {
  public static void main(String[] args) throws IOException {
    // boot up server using the route as defined below
    ActorSystem system = ActorSystem.create();

    // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
    final HighLevelServerExample app = new HighLevelServerExample();

    final Http http = Http.get(system);
    final ActorMaterializer materializer = ActorMaterializer.create(system);

    final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = app.createRoute().flow(system, materializer);
    final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer);

    System.out.println("Type RETURN to exit");
    System.in.read();
    
    binding
      .thenCompose(ServerBinding::unbind)
      .thenAccept(unbound -> system.terminate());
  }

  public Route createRoute() {
    // This handler generates responses to `/hello?name=XXX` requests
    Route helloRoute =
      parameterOptional("name", optName -> {
        String name = optName.orElse("Mister X");
        return complete("Hello " + name + "!");
      });

    return
      // here the complete behavior for this server is defined

      // only handle GET requests
      get(() -> route(
        // matches the empty path
        pathSingleSlash(() ->
          // return a constant string with a certain content type
          complete(HttpEntities.create(ContentTypes.TEXT_HTML_UTF8, "<html><body>Hello world!</body></html>"))
        ),
        path("ping", () ->
          // return a simple `text/plain` response
          complete("PONG!")
        ),
        path("hello", () ->
          // uses the route defined above
          helloRoute
        )
      ));
  }
}

The core of the Routing DSL becomes available with a single import:

import akka.http.javadsl.server.Directives.*;

Or by extending the akka.http.javadsl.server.AllDirectives class which brings together all directives into a single class for easier access:

extends AllDirectives

Of course it is possible to directly import only the directives you need (i.e. WebSocketDirectives etc).

Contents