Routing DSL Overview
Loading

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 = Util.getOrElse(uri.query().get("name"), "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.actor.ActorSystem;
import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.model.MediaTypes;
import akka.http.javadsl.server.*;
import akka.http.javadsl.server.values.Parameters;

import java.io.IOException;

public class HighLevelServerExample extends HttpApp {
    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
        new HighLevelServerExample().bindRoute("localhost", 8080, system);
        System.out.println("Type RETURN to exit");
        System.in.read();
        system.shutdown();
    }

    // A RequestVal is a type-safe representation of some aspect of the request.
    // In this case it represents the `name` URI parameter of type String.
    private RequestVal<String> name = Parameters.stringValue("name").withDefault("Mister X");

    @Override
    public Route createRoute() {
        // This handler generates responses to `/hello?name=XXX` requests
        Route helloRoute =
            handleWith1(name,
                // in Java 8 the following becomes simply
                // (ctx, name) -> ctx.complete("Hello " + name + "!")
                new Handler1<String>() {
                    @Override
                    public RouteResult apply(RequestContext ctx, String name) {
                        return ctx.complete("Hello " + name + "!");
                    }
                });

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

Heart of the high-level architecture is the route tree. It is a big expression of type Route that is evaluated only once during startup time of your service. It completely describes how your service should react to any request.

The type Route is the basic building block of the route tree. It defines if and a how a request should be handled. Routes are composed to form the route tree in the following two ways.

A route can be wrapped by a "Directive" which adds some behavioral aspect to its wrapped "inner route". path("ping") is such a directive that implements a path filter, i.e. it only passes control to its inner route when the unmatched path matches "ping". Directives can be more versatile than this: A directive can also transform the request before passing it into its inner route or transform a response that comes out of its inner route. It's a general and powerful abstraction that allows to package any kind of HTTP processing into well-defined blocks that can be freely combined. akka-http defines a library of predefined directives and routes for all the various aspects of dealing with HTTP requests and responses.

Read more about Directives.

The other way of composition is defining a list of Route alternatives. Alternative routes are tried one after the other until one route "accepts" the request and provides a response. Otherwise, a route can also "reject" a request, in which case further alternatives are explored. Alternatives are specified by passing a list of routes either to Directive.route() as in pathSingleSlash().route() or to directives that directly take a variable number of inner routes as argument like get() here.

Read more about Routes.

Another important building block is a RequestVal<T>. It represents a value that can be extracted from a request (like the URI parameter Parameters.stringValue("name") in the example) and which is then interpreted as a value of type T. Examples of HTTP aspects represented by a RequestVal are URI parameters, HTTP form fields, details of the request like headers, URI, the entity, or authentication data.

Read more about Request values.

The actual application-defined processing of a request is defined with a Handler instance or by specifying a handling method with reflection. A handler can receive the value of any request values and is converted into a Route by using one of the BasicDirectives.handleWith directives.

Read more about Handlers.

Requests or responses often contain data that needs to be interpreted or rendered in some way. Akka-http provides the abstraction of Marshaller and Unmarshaller that define how domain model objects map to HTTP entities.

Read more about Marshalling & Unmarshalling.

akka-http contains a testkit that simplifies testing routes. It allows to run test-requests against (sub-)routes quickly without running them over the network and helps with writing assertions on HTTP response properties.

Read more about Route Testkit.

Handling HTTP Server failures in the High-Level API

There are various situations when failure may occur while initialising or running an Akka HTTP server. Akka by default will log all these failures, however sometimes one may want to react to failures in addition to them just being logged, for example by shutting down the actor system, or notifying some external monitoring end-point explicitly.

Bind failures

For example the server might be unable to bind to the given port. For example when the port is already taken by another application, or if the port is privileged (i.e. only usable by root). In this case the "binding future" will fail immediatly, and we can react to if by listening on the Future's completion:

import akka.actor.ActorSystem;
import akka.dispatch.OnFailure;
import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.server.*;
import akka.http.javadsl.server.values.Parameters;
import akka.http.scaladsl.Http;
import scala.concurrent.Future;

import java.io.IOException;

@SuppressWarnings("unchecked")
public class HighLevelServerBindFailureExample {
    public static void main(String[] args) throws IOException {
        // boot up server using the route as defined below
        final ActorSystem system = ActorSystem.create();

        // HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
        Future<Http.ServerBinding> bindingFuture =
                new HighLevelServerExample().bindRoute("localhost", 8080, system);

        bindingFuture.onFailure(new OnFailure() {
            @Override
            public void onFailure(Throwable failure) throws Throwable {
                System.err.println("Something very bad happened! " + failure.getMessage());
                system.shutdown();
            }
        }, system.dispatcher());

        system.shutdown();
    }
}

Note

For a more low-level overview of the kinds of failures that can happen and also more fine-grained control over them refer to the Handling HTTP Server failures in the Low-Level API documentation.

Contents