Jersey / JAX-RS 2 AsyncResponse - 如何跟踪当前的长轮询呼叫者

时间:2014-10-29 06:44:45

标签: java web-services jersey jax-rs jersey-2.0

我的目标是支持多个网络服务呼叫者的长轮询,并跟踪哪些呼叫者当前"停放"长期民意调查(即连通)。通过长时间的民意调查,"我的意思是调用者调用Web服务并且服务器(Web服务)不会立即返回,但让调用者等待一段预设的时间(在我的应用程序中一小时),或者如果服务器有消息则更快返回发送给调用者(在这种情况下,服务器通过调用asyncResponse.resume返回消息(" MESSAGE"))。

我将此分为两个问题。

第一个问题:这是一种合理的方式来“停放”#34;长轮询的来电者?

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

这很好用。我不确定它是否遵循最佳做法。让"返回响应似乎很奇怪......"方法结束时的行。当呼叫者第一次连接时执行该行,但是,据我所知," COMPLETE"结果永远不会实际返回给调用者。来电者得到" TIMEOUT"当服务器需要通知调用者事件时,服务器通过asyncResponse.resume()发送的响应或其他响应消息。

第二个问题:我目前的挑战是准确反映HashMap中当前轮询的呼叫者的数量。当调用者停止轮询时,我需要从HashMap中删除它的条目。调用者可以离开有三个原因:1)经过3600秒并因此超时; 2)另一个应用程序组件在HashMap中查找调用者并调用asyncResponse.resume(" MESSAGE")和3 )HTTP连接因某些原因而中断,例如有人关闭运行客户端应用程序的计算机。

因此,JAX-RS有两个回调我可以注册以通知连接结束:CompletionCallback(我的结束轮询原因#1和#2)和ConnectionCallback(我的结束轮询原因#3)

我可以将这些添加到我的网络服务方法中:

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

正如我所说,挑战是使用这两个回调从HashMap中删除不再轮询的调用者。 ConnectionCallback实际上更容易两者。由于它接收asyncResponse实例作为参数,我可以使用它来从HashMap中删除相应的条目,如下所示:

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

对于CompletionCallback,因为asyncResponse在触发回调时已经完成或取消,所以没有传入asyncResponse参数。因此,似乎唯一的解决方案是运行HashMap条目检查完成/取消并删除它们,如下所示。 (请注意,我不需要知道调用者是因为resume()被调用还是因为超时而离开,所以我不会查看&#34; throwable&#34;参数)。 / p>

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

任何反馈都将不胜感激。这种方法看起来合理吗?是否有更好或更多的泽西/ JAX-RS方式来做到这一点?

1 个答案:

答案 0 :(得分:0)

您的poller()方法不需要返回Response以参与异步处理。它可以返回无效。如果您在轮询器中执行任何复杂操作,则应考虑将整个方法包装在try / catch块中,该块恢复AsyncResponse对象,但要确保任何RuntimeExceptions或其他未经检查的Throwable都不会丢失。在catch块中记录这些异常似乎也是一个好主意。

我目前正在研究如何可靠地捕获客户端取消异步请求的问题,并且已经阅读了一个问题,该问题表明该机制不适用于提问者[1]。我暂时将其留给其他人填写此信息。

[1] AsyncResponse ConnectionCallback does not fire in Jersey