diff --git a/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java b/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java index 62d3d3e136f..6acd8614678 100644 --- a/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java +++ b/akka-http-tests/src/test/java/akka/http/javadsl/server/CompleteTest.java @@ -9,6 +9,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import akka.http.javadsl.model.StatusCodes; import org.junit.Test; import akka.http.javadsl.marshallers.jackson.Jackson; @@ -70,4 +71,64 @@ public void completeWithFuture() { .assertStatusCode(200) .assertEntity("42 + 23 = 65"); } + + + private ExceptionHandler customExceptionHandler() { + return ExceptionHandler.newBuilder() + .match(IllegalStateException.class, ex -> + complete(StatusCodes.SERVICE_UNAVAILABLE, "Custom Error")) + .build(); + } + + private void checkRoute(Route route) { + Route sealedRoute = route.seal(RejectionHandler.defaultHandler(), customExceptionHandler()); + runRoute(sealedRoute, HttpRequest.GET("/crash")) + .assertStatusCode(StatusCodes.SERVICE_UNAVAILABLE) + .assertEntity("Custom Error"); + } + + @Test + public void completeOKWithFutureStringFailing() { + Route route = path("crash", () -> + completeOKWithFutureString(CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("Boom!"); + }))); + checkRoute(route); + } + + @Test + public void completeWithFutureStatusFailing() { + Route route = path("crash", () -> + completeWithFutureStatus(CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("Boom!"); + }))); + checkRoute(route); + } + + @Test + public void completeWithFutureFailing() { + Route route = path("crash", () -> + completeWithFuture(CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("Boom!"); + }))); + checkRoute(route); + } + + @Test + public void completeOKWithFutureFailing() { + Route route = path("crash", () -> + completeOKWithFuture(CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("Boom!"); + }))); + checkRoute(route); + } + + @Test + public void completeOKWithFutureTFailing() { + Route route = path("crash", () -> + completeOKWithFuture(CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("Boom!"); + }), Jackson.marshaller())); + checkRoute(route); + } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala index ee5a8765deb..dbcce3066c5 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/RouteDirectives.scala @@ -4,7 +4,7 @@ package akka.http.javadsl.server.directives -import java.util.concurrent.CompletionStage +import java.util.concurrent.{ CompletionException, CompletionStage } import akka.dispatch.ExecutionContexts import akka.http.javadsl.marshalling.Marshaller @@ -20,7 +20,7 @@ import akka.http.javadsl.model.RequestEntity import akka.http.javadsl.model.ResponseEntity import akka.http.javadsl.model.StatusCode import akka.http.javadsl.model.Uri -import akka.http.javadsl.server.{ RoutingJavaMapping, Rejection, Route } +import akka.http.javadsl.server.{ Rejection, Route, RoutingJavaMapping } import akka.http.scaladsl import akka.http.scaladsl.marshalling.Marshaller._ import akka.http.scaladsl.marshalling.ToResponseMarshallable @@ -239,7 +239,7 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeWithFuture(value: CompletionStage[HttpResponse]) = RouteAdapter { - D.complete(value.asScala.fast.map(_.asScala)) + D.complete(value.asScala.fast.map(_.asScala).recover(unwrapCompletionException)) } /** @@ -247,7 +247,7 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeOKWithFuture(value: CompletionStage[RequestEntity]) = RouteAdapter { - D.complete(value.asScala.fast.map(_.asScala)) + D.complete(value.asScala.fast.map(_.asScala).recover(unwrapCompletionException)) } /** @@ -255,7 +255,7 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeOKWithFutureString(value: CompletionStage[String]) = RouteAdapter { - D.complete(value.asScala) + D.complete(value.asScala.recover(unwrapCompletionException)) } /** @@ -263,7 +263,7 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeWithFutureStatus(status: CompletionStage[StatusCode]): Route = RouteAdapter { - D.complete(status.asScala.fast.map(_.asScala)) + D.complete(status.asScala.fast.map(_.asScala).recover(unwrapCompletionException)) } /** @@ -271,7 +271,7 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeOKWithFuture[T](value: CompletionStage[T], marshaller: Marshaller[T, RequestEntity]) = RouteAdapter { - D.complete(value.asScala.fast.map(v ⇒ ToResponseMarshallable(v)(fromToEntityMarshaller()(marshaller)))) + D.complete(value.asScala.fast.map(v ⇒ ToResponseMarshallable(v)(fromToEntityMarshaller()(marshaller))).recover(unwrapCompletionException)) } /** @@ -279,7 +279,16 @@ abstract class RouteDirectives extends RespondWithDirectives { */ @CorrespondsTo("complete") def completeWithFuture[T](value: CompletionStage[T], marshaller: Marshaller[T, HttpResponse]) = RouteAdapter { - D.complete(value.asScala.fast.map(v ⇒ ToResponseMarshallable(v)(marshaller))) + D.complete(value.asScala.fast.map(v ⇒ ToResponseMarshallable(v)(marshaller)).recover(unwrapCompletionException)) + } + + // TODO: This might need to be raised as an issue to scala-java8-compat instead. + // Right now, having this in Java: + // CompletableFuture.supplyAsync(() -> { throw new IllegalArgumentException("always failing"); }) + // will in fact fail the future with CompletionException. + private def unwrapCompletionException[T]: PartialFunction[Throwable, T] = { + case x: CompletionException if x.getCause ne null ⇒ + throw x.getCause } }