starter - Play Framework 2.5 JavaAsync lanzando CompletionException
play framework tutorial (1)
Estoy usando Play 2.5 para crear una aplicación simple. Para un mejor rendimiento, estoy usando la respuesta fragmentada de Akka con la estrategia Java 8 CompletionStage. A continuación se muestra el código por el cual se genera la respuesta fragmentada (funciona bien cuando no se usa CompeededFuture):
@Singleton
public class AbstractSource {
public Source<ByteString, ?> getChunked(String html) {
return Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
.mapMaterializedValue(sourceActor -> {
sourceActor.tell(ByteString.fromString(html), null);
sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
return null;
});
}
}
Y aquí está mi controlador:
@Singleton
@AddCSRFToken
public class Application extends Controller {
@Inject
private AbstractSource abstractSource;
public CompletionStage<Result> index() {
CompletionStage<Source<ByteString, ?>> source = CompletableFuture.supplyAsync(() ->
abstractSource.getChunked(index.render(CSRF.getToken(request()).map(t ->
t.value()).orElse("no token")).body()
)
);
return source.thenApply( chunks -> ok().chunked(chunks));
}
}
Ahora cuando estoy ejecutando la aplicación está lanzando siguiente excepción:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[CompletionException: java.lang.RuntimeException: There is no HTTP Context available from here.]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:269)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:195)
at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:98)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:99)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:98)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:344)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:343)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
Caused by: java.util.concurrent.CompletionException: java.lang.RuntimeException: There is no HTTP Context available from here.
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.RuntimeException: There is no HTTP Context available from here.
at play.mvc.Http$Context.current(Http.java:57)
at play.mvc.Controller.request(Controller.java:36)
at com.mabsisa.ui.web.controllers.Application.lambda$index$1(Application.java:31)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
... 5 common frames omitted
No estoy usando el contexto HTTP en ninguna parte, entonces por qué esto no funciona no lo estoy obteniendo. El mismo código funciona cuando se devuelve un resultado normal con una respuesta fragmentada. Por favor ayuda con esto
Debe proporcionar el contexto de ejecución HTTP cuando se trata de CompletableFuture
/ CompletionStage
. En Scala, la información de contexto se pasa a través de implícitos, estos no están disponibles en Java; esta es la razón por la cual Play usa ThreadLocal
.
Sin embargo, puede perder esta información cuando se cambian los hilos y es por eso que tiene el problema. Puede pensar que no tiene acceso al contexto HTTP, pero en realidad lo hace; está utilizando request()
.
Entonces debe cambiar su código para usar supplyAsync
con un Executor:
De esto:
CompletableFuture.supplyAsync(() -> abstractSource.getChunked(index.render(CSRF.getToken(request()).map(t ->
t.value()).orElse("no token")).body()
)
);
a esto:
CompletableFuture.supplyAsync(() -> abstractSource.getChunked(index.render(CSRF.getToken(request()).map(t ->
t.value()).orElse("no token")).body()
)
, ec.current());
donde ec
es su contexto: @Inject HttpExecutionContext ec;