2016-03-26 9 views
7

Ich benutze Play 2.5, um eine einfache App zu erstellen. Für eine bessere Leistung verwende ich Akka Chunked Response mit Java 8 CompletionStage Strategie. Unten ist der Code durch die Reaktion chunked erzeugt immer (es gut funktioniert, wenn nicht ComperableFuture verwenden):
Play Framework 2.5 JavaAsync werfen CompletionException

@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; 
       }); 

    } 

} 

Und hier ist mein Controller:

@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)); 

    } 

} 

Nun, wenn ich die renne app es folgende Ausnahme ist werfen:

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 

ich bin nicht überall HTTP Zusammenhang mit, warum dies nicht funktioniert I‘ Ich werde nicht bekommen. Derselbe Code funktioniert, wenn das normale Ergebnis mit der Chunked-Antwort zurückgegeben wird. Bitte helfen Sie mit diesem

Antwort

13

Sie haben liefern den HTTP-Ausführungskontext im Umgang mit CompletableFuture/CompletionStage. In Scala werden die Kontextinformationen über implicits übergeben, diese sind in Java nicht verfügbar - deshalb verwendet Play ThreadLocal.

Allerdings können Sie diese Informationen beim Wechseln von Threads verlieren und deshalb haben Sie das Problem. Sie können denken, dass Sie nicht auf den HTTP-Kontext zugreifen, aber tatsächlich tun Sie - Sie verwenden request().

So müssen Sie Ihren Code ändern supplyAsync mit einem Executor zu verwenden:

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#supplyAsync-java.util.function.Supplier-java.util.concurrent.Executor-

Daraus:

CompletableFuture.supplyAsync(() -> abstractSource.getChunked(index.render(CSRF.getToken(request()).map(t -> 
                t.value()).orElse("no token")).body() 
                ) 
               ); 

dazu:

CompletableFuture.supplyAsync(() -> abstractSource.getChunked(index.render(CSRF.getToken(request()).map(t -> 
                t.value()).orElse("no token")).body() 
                ) 
               , ec.current()); 

wo ec ist Ihr Kontext: @Inject HttpExecutionContext ec;

+0

ehrfürchtige Antwort mit guter Erklärung ...... eine weitere Sache möchte fragen, ob dies eine gute Design-Ansatz im Spiel ist oder nicht? Kannst du deine Meinung teilen? –

+1

Das Zurückgeben einer 'CompletionStage 'ist der _de facto_-Standard zum Definieren von asynchronen Aktionen in Play 2.5, damit es Ihnen hier gut geht. In Bezug auf Chunked-Antworten in Play: Werfen Sie einen Blick auf https://www.playframework.com/documentation/2.5.x/JavaStream#Chunked-responses - mit Akka-Streams sind Sie bereits auf einem guten Weg, also machen Sie sich auch keine Sorgen - Ihr Designansatz ist gut! – Anton

+0

danke einen Ton für den Rat @Anton –

0

ich zusätzlich zu Antons antwort.

Wenn Sie bauen nicht blockierenden Play-App Java-API verwenden, könnte es recht umständlich geworden HttpExecutionContext zu injizieren und übergeben ec.current()) jedes Mal, wenn Sie Methoden auf CompletionStage aufrufen müssen.

Um das Leben einfacher zu machen, können Sie einen Dekorateur verwenden, der den Kontext zwischen den Anrufen bewahrt.

public class ContextPreservingCompletionStage<T> implements CompletionStage<T> { 

    private HttpExecutionContext context; 
    private CompletionStage<T> delegate; 

    public ContextPreservingCompletionStage(CompletionStage<T> delegate, 
              HttpExecutionContext context) { 
     this.delegate = delegate; 
     this.context = context; 
    } 
    ... 
} 

So müssen Sie Kontext nur einmal passieren:

return new ContextPreservingCompletionStage<>(someCompletableFuture, context) 
          .thenCompose(something -> {...}); 
          .thenApply(something -> {...}); 

Statt

return someCompletableFuture.thenComposeAsync(something -> {...}, context.current()) 
          .thenApplyAsync(something -> {...}, context.current()); 

Das ist besonders nützlich, wenn Sie einen Multi-Tier-App bauen, und vorbei CompletionStage s zwischen verschiedenen Klassen.

Full-Dekorator-Implementierungsbeispiel is here.

+0

danke für den Vorschlag :) –

Verwandte Themen