Jersey / JAX-RS 2 AsyncResponse - How to Track Ongoing Calls with Long Poll

My goal is to maintain a long poll for multiple calling web services and keep track of which callers are currently "parked" on a long poll (ie connected). "Long polling" means the caller is calling the web service and the server (web service) does not return immediately, but holds the caller for some given period of time (an hour in my app) or returns earlier if the server has a message to send to the caller (in this case, the server returns a message by calling asyncResponse.resume ("MESSAGE")).

I'll break this down into two questions.

First question: is this a smart way to park callers who have been interviewed a long time ago?

@GET
@Produces(MediaType.TEXT_PLAIN)
@ManagedAsync
@Path("/poll/{id}")
public Response poller(@Suspended final AsyncResponse asyncResponse, @PathParam("id") String callerId) {

    // add this asyncResponse to a HashMap that is persisted across web service calls by Jersey.
    // other application components that may have a message to send to a caller will look up the
    // caller by callerId in this HashMap and call resume() on its asyncResponse.
    callerIdAsyncResponseHashMap.put(callerId, asyncResponse);

    asyncResponse.setTimeout(3600, TimeUnit.SECONDS);
    asyncResponse.setTimeoutHandler(new TimeoutHandler() {
        @Override
        public void handleTimeout(AsyncResponse asyncResponse) {
            asyncResponse.resume(Response.ok("TIMEOUT").build());
        }
    });
    return Response.ok("COMPLETE").build();
}

      

This works great. I'm just not sure if he is following the best practices. It seems strange to have a "return Response ..." line at the end of the method. This line is executed when the caller connects first, but as I understand it, the "COMPLETE" result is never returned to the user. The caller receives a "TIMEOUT" response or some other response message sent by the server via asyncResponse.resume () when the server needs to notify the caller of an event.

Second question: my current challenge is to accurately reflect the caller population currently in the HashMap. When the caller stops polling, I need to remove his entry from the HashMap. The caller can quit for three reasons: 1) 3600 seconds have elapsed and therefore time is up, 2) another application component looks for the caller in the HashMap and calls asyncResponse.resume ("MESSAGE"), and 3) HTTP connection for some reason for example, someone turned off a computer running a client application.

So JAX-RS has two callbacks that I can register to get notified when connections are complete: CompletionCallback (for my reasons for first poll # 1 and # 2 above) and ConnectionCallback (for reason for my third poll # 3 above) ...

I can add them to my web service method like this:

@GET
@Produces(MediaType.TEXT_PLAIN)
@ManagedAsync
@Path("/poll/{id}")
public Response poller(@Suspended final AsyncResponse asyncResponse, @PathParam("id") String callerId) {

    asyncResponse.register(new CompletionCallback() {
        @Override
        public void onComplete(Throwable throwable) {
            //?
        }
    });

    asyncResponse.register(new ConnectionCallback() {
        @Override
        public void onDisconnect(AsyncResponse disconnected) {
            //?
        }
    });

    // add this asyncResponse to a HashMap that is persisted across web service calls by Jersey.
    // other application components that may have a message to send to a caller will look up the
    // caller by callerId in this HashMap and call resume() on its asyncResponse.
    callerIdAsyncResponseHashMap.put(callerId, asyncResponse);

    asyncResponse.setTimeout(3600, TimeUnit.SECONDS);
    asyncResponse.setTimeoutHandler(new TimeoutHandler() {
        @Override
        public void handleTimeout(AsyncResponse asyncResponse) {
            asyncResponse.resume(Response.ok("TIMEOUT").build());
        }
    });
    return Response.ok("COMPLETE").build();
}

      

The problem, as I said, is using these two callbacks to remove longer polling callers from the HashMap. ConnectionCallback is actually the easier of the two. Since it receives an asyncResponse instance as a parameter, I can use it to remove the corresponding entry from the HashMap, for example:

asyncResponse.register(new ConnectionCallback() {
    @Override
    public void onDisconnect(AsyncResponse disconnected) {
        Iterator<Map.Entry<String, AsyncResponse>> iterator = callerIdAsyncResponseHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, AsyncResponse> entry = iterator.next();
            if (entry.getValue().equals(disconnected)) {
                iterator.remove();
                break;
            }
        }
    }
});

      

For CompletionCallback, though, because the asyncResponse has already been executed or canceled during the callback firing, no asyncResponse parameter is passed. As a result, the only solution seems to be to check the HashMap entries for done / undone and delete them as shown below. (Note that I don't need to know if the caller left because resume () was called, or because he was disabled, so I am not considering the "throwable" option).

asyncResponse.register(new CompletionCallback() {
    @Override
    public void onComplete(Throwable throwable) {
        Iterator<Map.Entry<String, AsyncResponse>> iterator = callerIdAsyncResponseHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, AsyncResponse> entry = iterator.next();
            if (entry.getValue().isDone() || entry.getValue().isCancelled()) {
                iterator.remove();
            }
        }
    }
});

      

Any feedback will be greatly appreciated. Is this approach sane? Is there a better or more Jersey / JAX-RS way?

+3


source to share


1 answer


Your poller () method does not need to return a response to participate in asynchronous processing. He can bring back emptiness. If you're doing anything tricky in the poller, however, you should consider wrapping the entire method in a try / catch block that resumes your AsyncResponse object, except that any RuntimeExceptions or other unchecked Throwables are not lost. Logging these exceptions in the catch block seems like a good idea here as well.

I am currently researching how to reliably catch an asynchronous request that is canceled by the client, and read on one question that suggests the mechanism is not working on the questionnaire [1]. I'll leave it to others to fill in this information for now.



[1] AsyncResponse ConnectionCallback does not fire on Jersey

0


source







All Articles