asynchronous WS calls and await() outside of a controller

774 kali dilihat
Langsung ke pesan pertama yang belum dibaca

Dan Serfaty

belum dibaca,
15 Des 2011, 18.05.3515/12/11
kepadaplay-framework

Hi all,
Is there a way to use the await() mechanism outside of a controller?

I do not wish to have code making the asynchronous call in the
controller, but instead have that code in a service class that can be
reused by multiple controllers however, there is no way to call await
outside of a controller since that method is protected.

So for example in the controller:

ServiceClass service = new My ServiceClass();
MyObject myObject= service.getMeAnObject();
render(myObject);

And the service class:

public class ServiceClass
{
...
public MyObject getMeAnObject()
{
String url = "http://...";
Promise<HttpResponse> promise = url(url).getAsync();

// do something here similar to await in a controller
// that suspends the code waiting for the response
// to be retrieved

HttpResponse response = promise.get();
return buildMyObjectFromResponse(reponse);
}
...
}

Is there a way to achieve something like that?

Note that I also posted this question to stack overflow and a user
advised me to make my service class extend Controller. While I don't
like this solution since the service class is not conceptually a
controller I gave it a try and it works when I use it from one
Controller, but fails with the following exception when used from
another controller, in an apparently similar situation (the one that
works is an html page rendered, the failing one is an api call
returning json data, both calls the service class). The failure is:

Caused by: play.exceptions.ContinuationsException: Cannot use
await/continuations when not all application classes on the callstack
are properly enhanced. The following class is not enhanced:
controllers.api.PrivateAPI
at play.mvc.Controller.verifyContinuationsEnhancement(Controller.java:
1028)
at play.mvc.Controller.await(Controller.java:991)
...

I think the goal is really to be able to encapsulate the full "getting
the data" mechanism somewhere outside the controller without having to
worry about how it is retrieved (through WS or any other mechanism)
but still be able to prevent holding the other requests while that
data is being retrieved.

Thanks for any help you can provide.

- Dan

Yann Simon

belum dibaca,
16 Des 2011, 03.34.5716/12/11
kepadaplay-fr...@googlegroups.com
Could you return your own Promise that uses the Promise<HttpResponse>
and make the await in the controller?

2011/12/16 Dan Serfaty <dser...@gmail.com>:

> --
> You received this message because you are subscribed to the Google Groups "play-framework" group.
> To post to this group, send email to play-fr...@googlegroups.com.
> To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.
>

Morten Kjetland

belum dibaca,
16 Des 2011, 03.40.4716/12/11
kepadaplay-fr...@googlegroups.com
Hi,


Yann's sugestion should work.

As long as you execute the await-method inside a controller it should work.

This is why you cannot use await outside a controller:

we have to enhance the java-code to enable continuations.. We only do this in controllers which explicit uses await.

-Morten

Dan Serfaty

belum dibaca,
16 Des 2011, 12.38.4016/12/11
kepadaplay-framework

Hi all,
Thanks for the suggestions, this evidently works but the point is that
I don't want the controller to have to know or worry about where the
data is coming from. If it is waiting on an HttpResponse then it has
to process it, which means if I want to use the call from several
controllers, I have to write some ugly utility method somewhere that
parses an HttpResponse instead of having a service class that returns
me just what I need. Furthermore, I may not necessarily be using WS to
get the data but the action may still take some time in which case I
would want to suspend the controller. Ideally I want the service to
handle all of this as a black box.

It is understandable that the await call is located in the controller
and maybe an better solution would be for the service to return a
Promise<MyObject> and have the controller wait for this but then
there's no way for me todo that is there?

So for example in the controller:
ServiceClass service = new My ServiceClass();

Promise<MyObject> promise = service.getMeAnObject();
MyObject myObject = await(promise);
render(myObject);

And the service class:
public class ServiceClass
{
...

public Promise<MyObject> getMeAnObject()


{
String url = "http://...";

// Somehow Build a Promise<MyObject>
}
...
}

Thanks,

- Dan

On Dec 16, 12:40 am, Morten Kjetland <morten.kjetl...@gmail.com>
wrote:


> Hi,
>
> Yann's sugestion should work.
>
> As long as you execute the await-method inside a controller it should work.
>
> This is why you cannot use await outside a controller:
>
> we have to enhance the java-code to enable continuations.. We only do this
> in controllers which explicit uses await.
>
> -Morten
>
>
>
>
>
>
>
> On Fri, Dec 16, 2011 at 9:34 AM, Yann Simon <yann.simon...@gmail.com> wrote:
> > Could you return your own Promise that uses the Promise<HttpResponse>
> > and make the await in the controller?
>

> > 2011/12/16 Dan Serfaty <dserf...@gmail.com>:

Dan Serfaty

belum dibaca,
16 Des 2011, 17.11.1116/12/11
kepadaplay-framework

I have taken example on what Play does in the WSAsync.WSAsyncRequest
class and this seems to work, however I have not been able to verify
yet that the request code was properly interrupted waiting for the
results. In addition I have no idea what the Play team recommends when
it comes to managing our own thread pools for what is not request
processing, so I would appreciate any insights you might have on this
possible solution.

In the controller:


ServiceClass service = new My ServiceClass();
Promise<MyObject> promise = service.getMeAnObject();
MyObject myObject = await(promise);
render(myObject);

And the service class:
public class ServiceClass
{

public Promise<MyObject> getMeAnObject()
{
String url = "http://...";

return execute(WS.url(url).getAsync());
}

private Promise<MyObject> execute(final Promise<HttpResponse>
promise)
{
try
{
final Promise<MyObject> smartFuture = new Promise<MyObject>();

ScheduledThreadPoolExecutor executor =
MyAppThreadPools.getServiceThreadPool();

executor.submit(new Runnable()
{
@Override
public void run()
{
try
{
smartFuture.invoke(new MyObject(promise.get()));
}
catch (Throwable e)
{
smartFuture.invokeWithException(e);
}
}
});
return smartFuture;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}

Thanks all.

- Dan

Scott Rippee

belum dibaca,
20 Agu 2013, 16.29.4620/08/13
kepadaplay-fr...@googlegroups.com, dser...@gmail.com
It's not pretty, but I added

        await("0s");

to the controller that is calling the other controller's protected method that uses uses WSAsync.  I'm thinking that this caused the bytecode injection to occur on that class and I no longer get the error:

play.exceptions.ContinuationsException: Cannot use await/continuations when not all application classes on the callstack are properly enhanced. The following class is not enhanced: ...

Scott Rippee

belum dibaca,
28 Okt 2014, 13.20.5428/10/14
kepadaplay-fr...@googlegroups.com, dser...@gmail.com
Dan, thanks for the await("0s").  It's not pretty, but allowed me to break some code apart that would have been far less pretty left in the original controllers.
Balas ke semua
Balas ke penulis
Teruskan
0 pesan baru