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 handlerString = new Handler() {
static final long serialVersionUID = 1L;
@Override
public RouteResult apply(RequestContext ctx) {
return ctx.complete(String.format("This was a %s request to %s",
ctx.request().method().value(), ctx.request().getUri()));
}
};
Handler handlerResponse = new Handler() {
static final long serialVersionUID = 1L;
@Override
public RouteResult apply(RequestContext ctx) {
// with full control over the returned HttpResponse:
final HttpResponse response = HttpResponse.create()
.withEntity(String.format("Accepted %s request to %s",
ctx.request().method().value(), ctx.request().getUri()))
.withStatus(StatusCodes.ACCEPTED);
return ctx.complete(response);
}
};
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 handlerString = new Handler() {
static final long serialVersionUID = 1L;
@Override
public RouteResult apply(RequestContext ctx) {
return ctx.complete(String.format("This was a %s request to %s",
ctx.request().method().value(), ctx.request().getUri()));
}
};
Handler handlerResponse = new Handler() {
static final long serialVersionUID = 1L;
@Override
public RouteResult apply(RequestContext ctx) {
// with full control over the returned HttpResponse:
final HttpResponse response = HttpResponse.create()
.withEntity(String.format("Accepted %s request to %s",
ctx.request().method().value(), ctx.request().getUri()))
.withStatus(StatusCodes.ACCEPTED);
return ctx.complete(response);
}
};
Route createRoute() {
return route(
get(
handleWith(handlerString)
),
post(
path("abc").route(
handleWith(handlerResponse)
)
)
);
}
}
// 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(202)
.assertEntity("Accepted 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 CompletionStage<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 CompletionStage<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 CompletionStage<Integer> multiply(final int x, final int y) {
return CompletableFuture.supplyAsync(() -> x * y);
}
public CompletionStage<Integer> add(final int x, final int y) {
return CompletableFuture.supplyAsync(() -> x + y);
}
}
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 CompletionStage<RouteResult>:
// would probably be injected or passed at construction time in real code
CalculatorService calculatorService = new CalculatorService();
public CompletionStage<RouteResult> multiplyAsync(final RequestContext ctx, int x, int y) {
CompletionStage<Integer> result = calculatorService.multiply(x, y);
return result.thenApplyAsync(product -> ctx.complete("x * y = " + product),
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 CompletionStage.thenApplyAsync and returns the resulting CompletionStage<RouteResult>. Note that you should always explicitly provide an executor that designates where the future transformation task is executed, using the JDK’s global ForkJoinPool is not recommended.
Otherwise, you can also still use handleWithN and use RequestContext.completeWith to "convert" a CompletionStage<RouteResult> into a RouteResult as shown here:
public RouteResult addAsync(final RequestContext ctx, int x, int y) {
CompletionStage<Integer> result = calculatorService.add(x, y);
return ctx.completeWith(result.thenApplyAsync(sum -> ctx.complete("x + y = " + sum),
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 CompletionStage<Integer> multiply(final int x, final int y) {
return CompletableFuture.supplyAsync(() -> x * y);
}
public CompletionStage<Integer> add(final int x, final int y) {
return CompletableFuture.supplyAsync(() -> x + y);
}
}
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 CompletionStage<RouteResult> multiplyAsync(final RequestContext ctx, int x, int y) {
CompletionStage<Integer> result = calculatorService.multiply(x, y);
return result.thenApplyAsync(product -> ctx.complete("x * y = " + product),
ctx.executionContext());
}
Route multiplyAsyncRoute =
path("multiply").route(
handleWithAsync2(xParam, yParam, this::multiplyAsync)
);
public RouteResult addAsync(final RequestContext ctx, int x, int y) {
CompletionStage<Integer> result = calculatorService.add(x, y);
return ctx.completeWith(result.thenApplyAsync(sum -> ctx.complete("x + y = " + sum),
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