diff --git a/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst b/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst index 885c306b15..2a89011d82 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/handlers.rst @@ -102,4 +102,42 @@ static methods. The referenced method must be publicly accessible. Deferring Result Creation ------------------------- -TODO \ No newline at end of file +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``, 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`` as an argument. + +This is demonstrated in the following example. Consider a asynchronous service defined like this +(making use of Java 8 lambdas): + +.. includecode:: /../../akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java + :include: async-service-definition + +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``: + +.. includecode:: /../../akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java + :include: async-handler-1 + +The handler invokes the service and then maps the calculation result to a ``RouteResult`` using ``Future.map`` and +returns the resulting ``Future``. + +Otherwise, you can also still use ``handleWithN`` and use ``RequestContext.completeWith`` to "convert" a +``Future`` into a ``RouteResult`` as shown here: + +.. includecode:: /../../akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java + :include: async-handler-2 + +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: + +.. includecode:: /../../akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java + :include: async-example-full diff --git a/akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java b/akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java index 6a766cdbc2..57ad1f807b 100644 --- a/akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java +++ b/akka-http-tests-java8/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java @@ -4,14 +4,19 @@ package docs.http.javadsl.server; -import akka.dispatch.Futures; +import akka.dispatch.Mapper; import akka.http.javadsl.model.HttpRequest; import akka.http.javadsl.server.*; import akka.http.javadsl.server.values.Parameters; import akka.http.javadsl.server.values.PathMatchers; import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.TestRoute; +import akka.japi.function.Function; import org.junit.Test; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; + +import java.util.concurrent.Callable; public class HandlerExampleDocTest extends JUnitRouteTest { @Test @@ -212,4 +217,83 @@ public class HandlerExampleDocTest extends JUnitRouteTest { .assertEntity("x * y = 115"); //#reflective-example-full } + + @Test + public void testDeferredResultAsyncHandler() { + //#async-example-full + //#async-service-definition + class CalculatorService { + public Future multiply(final int x, final int y, ExecutionContext ec) { + return akka.dispatch.Futures.future(() -> x * y, ec); + } + + public Future add(final int x, final int y, ExecutionContext ec) { + return akka.dispatch.Futures.future(() -> x + y, ec); + } + } + //#async-service-definition + + class TestHandler extends akka.http.javadsl.server.AllDirectives { + RequestVal xParam = Parameters.intValue("x"); + RequestVal yParam = Parameters.intValue("y"); + + //#async-handler-1 + // would probably be injected or passed at construction time in real code + CalculatorService calculatorService = new CalculatorService(); + public Future multiplyAsync(final RequestContext ctx, int x, int y) { + Future result = calculatorService.multiply(x, y, ctx.executionContext()); + Mapper func = new Mapper() { + @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) + ); + //#async-handler-1 + + //#async-handler-2 + public RouteResult addAsync(final RequestContext ctx, int x, int y) { + Future result = calculatorService.add(x, y, ctx.executionContext()); + Mapper func = new Mapper() { + @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) + ); + //#async-handler-2 + + 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"); + //#async-example-full + } }