使用Jersey SSE进行广播:检测已关闭的连接

时间:2015-09-24 20:47:36

标签: java jersey server-sent-events

我认为此问题与Server sent event with Jersey: EventOutput is not closed after client drops不重复,但可能与Jersey Server-Sent Events - write to broken connection does not throw exception有关。

在泽西文档的chapter 15.4.2中,描述了SseBroadcaster

  

但是,SseBroadcaster在内部识别并处理客户端断开连接。当客户端关闭连接时,广播公司会检测到这种情况,并从已注册的EventOutputs的内部集合中删除过时的连接,并释放与过时连接相关的所有服务器端资源。

我无法证实这一点。在下面的测试用例中,我看到永远不会调用子类SseBroadcaster的{​​{1}}方法:当onClose()关闭时,而不是在广播另一条消息时。

EventInput

也许public class NotificationsResourceTest extends JerseyTest { final static Logger log = LoggerFactory.getLogger(NotificationsResourceTest.class); final static CountingSseBroadcaster broadcaster = new CountingSseBroadcaster(); public static class CountingSseBroadcaster extends SseBroadcaster { final AtomicInteger connectionCounter = new AtomicInteger(0); public EventOutput createAndAttachEventOutput() { EventOutput output = new EventOutput(); if (add(output)) { int cons = connectionCounter.incrementAndGet(); log.debug("Active connection count: "+ cons); } return output; } @Override public void onClose(final ChunkedOutput<OutboundEvent> output) { int cons = connectionCounter.decrementAndGet(); log.debug("A connection has been closed. Active connection count: "+ cons); } @Override public void onException(final ChunkedOutput<OutboundEvent> chunkedOutput, final Exception exception) { log.trace("An exception has been detected", exception); } public int getConnectionCount() { return connectionCounter.get(); } } @Path("notifications") public static class NotificationsResource { @GET @Produces(SseFeature.SERVER_SENT_EVENTS) public EventOutput subscribe() { log.debug("New stream subscription"); EventOutput eventOutput = broadcaster.createAndAttachEventOutput(); return eventOutput; } } @Override protected Application configure() { ResourceConfig config = new ResourceConfig(NotificationsResource.class); config.register(SseFeature.class); return config; } @Test public void test() throws Exception { // check that there are no connections assertEquals(0, broadcaster.getConnectionCount()); // connect subscriber log.info("Connecting subscriber"); EventInput eventInput = target("notifications").request().get(EventInput.class); assertFalse(eventInput.isClosed()); // now there are connections assertEquals(1, broadcaster.getConnectionCount()); // push data log.info("Broadcasting data"); String payload = UUID.randomUUID().toString(); OutboundEvent chunk = new OutboundEvent.Builder() .mediaType(MediaType.TEXT_PLAIN_TYPE) .name("message") .data(payload) .build(); broadcaster.broadcast(chunk); // read data log.info("Reading data"); InboundEvent inboundEvent = eventInput.read(); assertNotNull(inboundEvent); assertEquals(payload, inboundEvent.readData()); // close subscription log.info("Closing subscription"); eventInput.close(); assertTrue(eventInput.isClosed()); // at this point, the subscriber has disconnected itself, // but jersey doesnt realise that assertEquals(1, broadcaster.getConnectionCount()); // wait, give TCP a chance to close the connection log.debug("Sleeping for some time"); Thread.sleep(10000); // push data again, this should really flush out the not-connected client log.info("Broadcasting data again"); broadcaster.broadcast(chunk); Thread.sleep(100); // there is no subscriber anymore assertEquals(0, broadcaster.getConnectionCount()); // FAILS! } } 不是测试它的好方法。在使用JavaScript JerseyTest的较少临床设置中,我看到EventSource被调用,但仅在先前关闭的连接上广播消息之后。

我做错了什么?

为什么没有onClose()检测到客户端关闭连接?

后续

我发现JERSEY-2833已被 Works设计拒绝了:

  

根据15.4.1中SSE章节(https://jersey.java.net/documentation/latest/sse.html)中的泽西岛文档,它提到泽西岛没有明确关闭连接,它是资源方法或者资源方法的责任。客户端。

这究竟是什么意思?资源是否应该执行超时并终止所有活动和已关闭的客户端连接?

3 个答案:

答案 0 :(得分:1)

在构造函数org.glassfish.jersey.media.sse.SseBroadcaster.SseBroadcaster()的文档中,它说:

  

创建一个新实例。如果此子构造函数由子类调用,则它假定子类存在的原因是实现onClose(org.glassfish.jersey.server.ChunkedOutput)onException(org.glassfish.jersey.server.ChunkedOutput, Exception)方法,因此它将新创建的实例添加为侦听器。为了避免这种情况,子类可以调用SseBroadcaster(Class)将它们的类作为参数传递。

因此,您不应该保留默认构造函数,并尝试使用您的类实现构造函数调用super:

public CountingSseBroadcaster(){
    super(CountingSseBroadcaster.class);
}

答案 1 :(得分:0)

我认为在资源上设置超时并仅杀死该连接可能会更好,例如:

@Path("notifications")
public static class NotificationsResource {

    @GET
    @Produces(SseFeature.SERVER_SENT_EVENTS)
    public EventOutput subscribe() {
        log.debug("New stream subscription");

        EventOutput eventOutput = broadcaster.createAndAttachEventOutput();
        new Timer().schedule( new TimerTask()
        {
            @Override public void run()
            {
               eventOutput.close()
            }
        }, 10000); // 10 second timeout
        return eventOutput;
    }
}   

答案 2 :(得分:0)

我想知道通过子类化你是否可能改变了行为。

    @Override
    public void onClose(final ChunkedOutput<OutboundEvent> output) {
        int cons = connectionCounter.decrementAndGet();
        log.debug("A connection has been closed. Active connection count: "+ cons);
    }

在此,您不要关闭ChunkedOutput,以免它释放连接。这可能是问题吗?