4
votes

Play 2 allows you to do async webservice calls through AsyncResult which won't block the thread:

public static Result feedTitle(String feedUrl) {
    return async(
        WS.url(feedUrl).get().map(
            new Function<WS.Response, Result>() {
                public Result apply(WS.Response response) {
                    return ok("Feed title:" + response.asJson().findPath("title"));
                }
            }
        )
    );
}

This only works if you're doing simple things like passing the result of the WS call straight through to the user. However, what if you have to do additional operations on the result?

Looking at the documentation, it seems like you can do this:

Promise<Response> promise = WS.url("http://some.website.com").get();
Response response = promise.get();    // I've got the result, but I've also blocked

Which obviously is not ideal. Is there a way of doing async calls while allowing Play to pass execution to other threads?

4

4 Answers

1
votes

Have a look at https://github.com/jroper/play-promise-presentation. This really cleared things up for me on how to design a system that could have multiple promise calls, etc, and manipulate the various promise responses into what is needed for a more complicated response, etc.

The best part is - the example doesn't feel too verbose. It reads very well and is pretty straightforward to understand.

0
votes

Ok I've found a solution, albeit a bit verbose. What you can do is move everything onto an Actor.

Setup your Actor in your Global object but save the ActorRef somewhere that you can access.

ActorRef myActor = Akka.system().actorOf(new Props(MyActor.class));

In your Actor, do the WS call.

public void onReceive(Object message) {
    WSRequestHolder request = WS.url("http://example.com");
    Response response = request.get().get();
    SomeResult result = doFurtherProcessing(response);
    getContext().sender().tell(result);    // reply the asking thread
}

And in your controller, simply wrap the call to your Actor in async()

public static Result method() {
    String message = "hello";
    return async(
        Akka.asPromise(ask(myActor, message, 1000)).map(new Function<Object, Result>() {
            public Result apply(Object result) throws Throwable {
                return ok(result);
            }
        })
    );
}

Reference: http://www.playframework.org/documentation/2.0.3/JavaAkka

0
votes

There is no need to create actors just to defer computation. You can bind post-processing to asynchronous calls using Promise.map(Function<A, B>) and Promise.flatMap(Function<<A>, Promise<B>>). These calls are chainable.

Example:

return async(WS.url("http://someservice.com/").get().map(
        new F.Function<play.libs.WS.Response, SomeData>() {
            @Override
            public SomeData apply (play.libs.WS.Response response) throws Throwable {
                SomeData someData = computeData(response);
                // Do extra computing here
                return someData;
            }
        }).map(
        new F.Function<SomeData, Result>() {
            @Override
            public Result apply(SomeData someData) throws Throwable {
                Result result = doSomethingElse(someData);
                return ok(result); 
            }
        })
 );

As long as the last map returns a Result, you are good to go.

0
votes

Alternative answer for play2

String url1 = String.format( "http://localhost:8080/site1", langcode );
String url2 = String.format( "http://localhost:8080/site2", langcode );
String url3 = String.format( "http://localhost:8080/site3", langcode );

F.Promise<WS.Response> remoteCall1 = WS.url(url1).get();
F.Promise<WS.Response> remoteCall2 = WS.url(url2).get();
F.Promise<WS.Response> remoteCall3 = WS.url(url3).get();

F.Promise<java.util.List<WS.Response>> promisesSequence = F.Promise.sequence(remoteCall1, remoteCall2, remoteCall3);

F.Promise<Result> resultPromise = promisesSequence.map(
    new Function<java.util.List<WS.Response>, Result>(){
        @Override
        public Result apply(java.util.List<WS.Response> responses){
            final StringBuffer sb = new StringBuffer();
            for(WS.Response r : responses){
                sb.append(r.getBody());
            }
            return ok( main.render("Output", sb.toString()));
        }
    });