Handlers
Loading

Handlers

Handlers implement the actual application-defined logic for a certain trace in the routing tree. Most of the leaves of the routing tree will be routes created from handlers. Creating a Route from a handler is achieved using the BasicDirectives.handleWith overloads. They come in several forms:

  • with a single Handler argument and a variable number of RequestVal<?> (may be 0)
  • with a number n of RequestVal<T1> arguments and a HandlerN<T1, .., TN> argument
  • with a Class<?> and/or instance and a method name String argument and a variable number of RequestVal<?> (may be 0) arguments

Simple Handler

In its simplest form a Handler is a SAM class that defines application behavior by inspecting the RequestContext and returning a RouteResult:

trait Handler extends akka.japi.function.Function[RequestContext, RouteResult] {
  override def apply(ctx: RequestContext): RouteResult
}

Such a handler inspects the RequestContext it receives and uses the RequestContext's methods to create a response:

Handler handler = new Handler() {
    static final long serialVersionUID = 1L;
    @Override
    public RouteResult apply(RequestContext ctx) {
        return ctx.complete("This was a " + ctx.request().method().value()  +
                " request to "+ctx.request().getUri());
    }
};

The handler can include any kind of logic but must return a RouteResult in the end which can only be created by using one of the RequestContext methods.

A handler instance can be used once or several times as shown in the full example:

class TestHandler extends akka.http.javadsl.server.AllDirectives {
    Handler handler = new Handler() {
        static final long serialVersionUID = 1L;
        @Override
        public RouteResult apply(RequestContext ctx) {
            return ctx.complete("This was a " + ctx.request().method().value()  +
                    " request to "+ctx.request().getUri());
        }
    };

    Route createRoute() {
        return route(
            get(
                handleWith(handler)
            ),
            post(
                path("abc").route(
                    handleWith(handler)
                )
            )
        );
    }
}

// actual testing code
TestRoute r = testRoute(new TestHandler().createRoute());
r.run(HttpRequest.GET("/test"))
    .assertStatusCode(200)
    .assertEntity("This was a GET request to http://example.com/test");

r.run(HttpRequest.POST("/test"))
    .assertStatusCode(404);

r.run(HttpRequest.POST("/abc"))
    .assertStatusCode(200)
    .assertEntity("This was a POST request to http://example.com/abc");

Handlers and Request Values

In many cases, instead of manually inspecting the request, a handler will make use of Request values to extract details from the request. This is possible using one of the other handleWith overloads that bind the values of one or more request values with a HandlerN instance to produce a Route:

final Handler2<Integer, Integer> multiply =
    new Handler2<Integer, Integer>() {
        static final long serialVersionUID = 1L;
        @Override
        public RouteResult apply(RequestContext ctx, Integer x, Integer y) {
            int result = x * y;
            return ctx.complete("x * y = " + result);
        }
    };

final Route multiplyXAndYParam = handleWith2(xParam, yParam, multiply);

The handler here implements multiplication of two integers. However, it doesn't need to specify where these parameters come from. In handleWith, as many request values of the matching type have to be specified as the handler needs. This can be seen in the full example:

class TestHandler extends akka.http.javadsl.server.AllDirectives {
    final RequestVal<Integer> xParam = Parameters.intValue("x");
    final RequestVal<Integer> yParam = Parameters.intValue("y");

    final RequestVal<Integer> xSegment = PathMatchers.intValue();
    final RequestVal<Integer> ySegment = PathMatchers.intValue();

    final Handler2<Integer, Integer> multiply =
        new Handler2<Integer, Integer>() {
            static final long serialVersionUID = 1L;
            @Override
            public RouteResult apply(RequestContext ctx, Integer x, Integer y) {
                int result = x * y;
                return ctx.complete("x * y = " + result);
            }
        };

    final Route multiplyXAndYParam = handleWith2(xParam, yParam, multiply);

    Route createRoute() {
        return route(
            get(
                pathPrefix("calculator").route(
                    path("multiply").route(
                        multiplyXAndYParam
                    ),
                    path("path-multiply", xSegment, ySegment).route(
                        handleWith2(xSegment, ySegment, multiply)
                    )
                )
            )
        );
    }
}

// actual testing code
TestRoute r = testRoute(new TestHandler().createRoute());
r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42"))
    .assertStatusCode(200)
    .assertEntity("x * y = 504");

r.run(HttpRequest.GET("/calculator/path-multiply/23/5"))
    .assertStatusCode(200)
    .assertEntity("x * y = 115");

Here, the handler is again being reused. First, in creating a route that expects URI parameters x and y. This route is then used in the route structure. And second, the handler is used with another set of RequestVal in the route structure, this time representing segments from the URI path.

Handlers in Java 8

Handlers are in fact simply classes which extend akka.japi.function.FunctionN in order to make reasoning about the number of handled arguments easier. For example, a Handler1[String] is simply a Function2[RequestContext, String, RouteResult]. You can think of handlers as hot-dogs, where each T type represents a sausage, put between the "buns" which are RequestContext and RouteResult.

In Java 8 handlers can be provided as function literals or method references. The previous example can then be written like this:

class TestHandler extends akka.http.javadsl.server.AllDirectives {
    final RequestVal<Integer> xParam = Parameters.intValue("x");
    final RequestVal<Integer> yParam = Parameters.intValue("y");

    final Handler2<Integer, Integer> multiply =
            (ctx, x, y) -> ctx.complete("x * y = " + (x * y));

    final Route multiplyXAndYParam = handleWith2(xParam, yParam, multiply);

    RouteResult subtract(RequestContext ctx, int x, int y) {
        return ctx.complete("x - y = " + (x - y));
    }

    Route createRoute() {
        return route(
            get(
                pathPrefix("calculator").route(
                    path("multiply").route(
                        // use Handler explicitly
                        multiplyXAndYParam
                    ),
                    path("add").route(
                        // create Handler as lambda expression
                        handleWith2(xParam, yParam,
                                (ctx, x, y) -> ctx.complete("x + y = " + (x + y)))
                    ),
                    path("subtract").route(
                        // create handler by lifting method
                        handleWith2(xParam, yParam, this::subtract)
                    )
                )
            )
        );
    }
}

// actual testing code
TestRoute r = testRoute(new TestHandler().createRoute());
r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42"))
        .assertStatusCode(200)
        .assertEntity("x * y = 504");

r.run(HttpRequest.GET("/calculator/add?x=12&y=42"))
        .assertStatusCode(200)
        .assertEntity("x + y = 54");

r.run(HttpRequest.GET("/calculator/subtract?x=42&y=12"))
        .assertStatusCode(200)
        .assertEntity("x - y = 30");

Note

The reason the handleWith## methods include the number of handled values is because otherwise (if overloading would be used, for all 22 methods) error messages generated by javac end up being very long and not readable, i.e. if one type of a handler does not match the given values, all possible candidates would be printed in the error message (22 of them), instead of just the one arity-matching method, pointing out that the type does not match.

We opted for better error messages as we feel this is more helpful when developing applications, instead of having one overloaded method which looks nice when everything works, but procudes hard to read error messages if something does not match up.

Providing Handlers by Reflection

Using Java before Java 8, writing out handlers as (anonymous) classes can be unwieldy. Therefore, handleReflectively overloads are provided that allow writing handler as simple methods and specifying them by name:

public RouteResult multiply(RequestContext ctx, Integer x, Integer y) {
    int result = x * y;
    return ctx.complete("x * y = " + result);
}

Route multiplyXAndYParam = handleReflectively(this, "multiply", xParam, yParam);

The complete calculator example can then be written like this:

class TestHandler extends akka.http.javadsl.server.AllDirectives {
    RequestVal<Integer> xParam = Parameters.intValue("x");
    RequestVal<Integer> yParam = Parameters.intValue("y");

    RequestVal<Integer> xSegment = PathMatchers.intValue();
    RequestVal<Integer> ySegment = PathMatchers.intValue();


    public RouteResult multiply(RequestContext ctx, Integer x, Integer y) {
        int result = x * y;
        return ctx.complete("x * y = " + result);
    }

    Route multiplyXAndYParam = handleReflectively(this, "multiply", xParam, yParam);

    Route createRoute() {
        return route(
            get(
                pathPrefix("calculator").route(
                    path("multiply").route(
                        multiplyXAndYParam
                    ),
                    path("path-multiply", xSegment, ySegment).route(
                        handleWith2(xSegment, ySegment, this::multiply)
                    )
                )
            )
        );
    }
}

// actual testing code
TestRoute r = testRoute(new TestHandler().createRoute());
r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42"))
    .assertStatusCode(200)
    .assertEntity("x * y = 504");

r.run(HttpRequest.GET("/calculator/path-multiply/23/5"))
    .assertStatusCode(200)
    .assertEntity("x * y = 115");

There are alternative overloads for handleReflectively that take a Class instead of an object instance to refer to static methods. The referenced method must be publicly accessible.

Deferring Result Creation

Sometimes a handler cannot directly complete the request but needs to do some processing asynchronously. In this case the completion of a request needs to be deferred until the result has been generated. This is supported by the routing DSL in two ways: either you can use one of the handleWithAsyncN methods passing an AsyncHandlerN which returns a Future<RouteResult>, i.e. an eventual RouteResult, or you can also use a regular handler as shown above and use RequestContext.completeWith for completion which takes an Future<RouteResult> as an argument.

This is demonstrated in the following example. Consider a asynchronous service defined like this (making use of Java 8 lambdas):

class CalculatorService {
    public Future<Integer> multiply(final int x, final int y, ExecutionContext ec) {
        return akka.dispatch.Futures.future(() -> x * y, ec);
    }

    public Future<Integer> add(final int x, final int y, ExecutionContext ec) {
        return akka.dispatch.Futures.future(() -> x + y, ec);
    }
}

Here the calculator runs the actual calculation in the background and only eventually returns the result. The HTTP service should provide a front-end to that service without having to block while waiting for the results. As explained above this can be done in two ways.

First, you can use handleWithAsyncN to be able to return a Future<RouteResult>:

// would probably be injected or passed at construction time in real code
CalculatorService calculatorService = new CalculatorService();
public Future<RouteResult> multiplyAsync(final RequestContext ctx, int x, int y) {
    Future<Integer> result = calculatorService.multiply(x, y, ctx.executionContext());
    Mapper<Integer, RouteResult> func = new Mapper<Integer, RouteResult>() {
        @Override
        public RouteResult apply(Integer product) {
            return ctx.complete("x * y = " + product);
        }
    }; // cannot be written as lambda, unfortunately
    return result.map(func, ctx.executionContext());
}
Route multiplyAsyncRoute =
    path("multiply").route(
        handleWithAsync2(xParam, yParam, this::multiplyAsync)
    );

The handler invokes the service and then maps the calculation result to a RouteResult using Future.map and returns the resulting Future<RouteResult>.

Otherwise, you can also still use handleWithN and use RequestContext.completeWith to "convert" a Future<RouteResult> into a RouteResult as shown here:

public RouteResult addAsync(final RequestContext ctx, int x, int y) {
    Future<Integer> result = calculatorService.add(x, y, ctx.executionContext());
    Mapper<Integer, RouteResult> func = new Mapper<Integer, RouteResult>() {
        @Override
        public RouteResult apply(Integer sum) {
            return ctx.complete("x + y = " + sum);
        }
    }; // cannot be written as lambda, unfortunately
    return ctx.completeWith(result.map(func, ctx.executionContext()));
}
Route addAsyncRoute =
    path("add").route(
        handleWith2(xParam, yParam, this::addAsync)
    );

Using this style, you can decide in your handler if you want to return a direct synchronous result or if you need to defer completion.

Both alternatives will not block and show the same runtime behavior.

Here's the complete example:

class CalculatorService {
    public Future<Integer> multiply(final int x, final int y, ExecutionContext ec) {
        return akka.dispatch.Futures.future(() -> x * y, ec);
    }

    public Future<Integer> add(final int x, final int y, ExecutionContext ec) {
        return akka.dispatch.Futures.future(() -> x + y, ec);
    }
}

class TestHandler extends akka.http.javadsl.server.AllDirectives {
    RequestVal<Integer> xParam = Parameters.intValue("x");
    RequestVal<Integer> yParam = Parameters.intValue("y");

    // would probably be injected or passed at construction time in real code
    CalculatorService calculatorService = new CalculatorService();
    public Future<RouteResult> multiplyAsync(final RequestContext ctx, int x, int y) {
        Future<Integer> result = calculatorService.multiply(x, y, ctx.executionContext());
        Mapper<Integer, RouteResult> func = new Mapper<Integer, RouteResult>() {
            @Override
            public RouteResult apply(Integer product) {
                return ctx.complete("x * y = " + product);
            }
        }; // cannot be written as lambda, unfortunately
        return result.map(func, ctx.executionContext());
    }
    Route multiplyAsyncRoute =
        path("multiply").route(
            handleWithAsync2(xParam, yParam, this::multiplyAsync)
        );

    public RouteResult addAsync(final RequestContext ctx, int x, int y) {
        Future<Integer> result = calculatorService.add(x, y, ctx.executionContext());
        Mapper<Integer, RouteResult> func = new Mapper<Integer, RouteResult>() {
            @Override
            public RouteResult apply(Integer sum) {
                return ctx.complete("x + y = " + sum);
            }
        }; // cannot be written as lambda, unfortunately
        return ctx.completeWith(result.map(func, ctx.executionContext()));
    }
    Route addAsyncRoute =
        path("add").route(
            handleWith2(xParam, yParam, this::addAsync)
        );

    Route createRoute() {
        return route(
            get(
                pathPrefix("calculator").route(
                    multiplyAsyncRoute,
                    addAsyncRoute
                )
            )
        );
    }
}

// testing code
TestRoute r = testRoute(new TestHandler().createRoute());
r.run(HttpRequest.GET("/calculator/multiply?x=12&y=42"))
    .assertStatusCode(200)
    .assertEntity("x * y = 504");

r.run(HttpRequest.GET("/calculator/add?x=23&y=5"))
    .assertStatusCode(200)
    .assertEntity("x + y = 28");

Contents