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

我的目标是支持对多个Web服务调用者进行长轮询,并跟踪在长轮询(即已连接)上当前“驻留”在哪些调用者上。 “长时间轮询”是指调用者调用Web服务,并且服务器(Web服务)不会立即返回,而是使调用者等待某个预设时间段(在我的应用程序中为一小时),或者更快返回如果服务器有一条消息要发送给调用方(在这种情况下,服务器将通过调用asyncResponse.resume(“ MESSAGE”)返回该消息)。

我将分为两个问题。

第一个问题:这是“保留”长时间轮询的呼叫者的合理方法吗?

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

这很好。我只是不确定它是否遵循最佳实践。在方法末尾出现“ return Response ...”行似乎很奇怪。这行是在呼叫者首次连接时执行的,但是据我所知,“ COMPLETE”结果实际上不会返回给呼叫者。当服务器需要将事件通知给调用者时,调用者将获得“超时”响应或服务器通过asyncResponse.resume()发送的其他响应消息。

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

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

我可以将它们添加到我的Web服务方法中,如下所示:

@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()或因为超时而离开了调用方,所以我不必查看“ throwable”参数)。

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

对于任何反馈,我们都表示感谢。这种方法看起来合理吗?是否有更好或更多的Jersey / JAX-RS方法呢?

参考方案

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

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

[1] AsyncResponse ConnectionCallback does not fire in Jersey

代理服务器上的JAX-WS客户端认证 - java

我正在尝试使用JAX-WS api在客户端应用程序上发送一些肥皂消息。但是,我位于防火墙后面,唯一的选择是使用代理服务器进行访问。我试图在Google上找到有关此问题的任何答案,但到目前为止都失败了:要对System.setProperty,http.proxyHost,http.proxyPort,http.proxyUser使用http.proxyPas…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…

Java-如何将此字符串转换为日期? - java

我从服务器收到此消息,我不明白T和Z的含义,2012-08-24T09:59:59Z将此字符串转换为Date对象的正确SimpleDateFormat模式是什么? java大神给出的解决方案 这是ISO 8601标准。您可以使用SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM…

Java:从类中查找项目名称 - java

仅通过类的实例,如何使用Java反射或类似方法查找项目名称?如果不是,项目名称(我真正想要的是)可以找到程序包名称吗? 参考方案 项目只是IDE使用的简单组织工具,因此项目名称不是类或JVM中包含的信息。要获取软件包,请使用Class#getPackage()。然后,可以调用Package#getName()将包作为您在代码的包声明中看到的String来获取…