Java 使用SSE进行广播:检测闭合连接

Java 使用SSE进行广播:检测闭合连接,java,jersey,server-sent-events,Java,Jersey,Server Sent Events,我相信这个问题不是重复的,但可能与 在泽西岛文件中,描述了: 但是,SSE广播公司在内部识别并处理客户端断开连接。当客户端关闭连接时,广播方会检测到这一点,并从已注册EventOutputs的内部集合中删除陈旧连接,同时释放与陈旧连接关联的所有服务器端资源 我不能证实这一点。在下面的测试用例中,我看到子类SseBroadcaster的onClose()方法从未被调用:当EventInput关闭时没有调用,当另一条消息被广播时也没有调用 public class NotificationsReso

我相信这个问题不是重复的,但可能与

在泽西岛文件中,描述了:

但是,SSE广播公司在内部识别并处理客户端断开连接。当客户端关闭连接时,广播方会检测到这一点,并从已注册EventOutputs的内部集合中删除陈旧连接,同时释放与陈旧连接关联的所有服务器端资源

我不能证实这一点。在下面的测试用例中,我看到子类
SseBroadcaster
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!
    }
}
公共类通知源测试扩展了Jersey测试{
最终静态记录器日志=LoggerFactory.getLogger(NotificationsResourceTest.class);
最终静态计数SEBROADCASTER广播机=新计数SEBROADCASTER();
公共静态类计数SEBROADCASTER扩展SSE广播程序{
最终AtomicInteger连接计数器=新的AtomicInteger(0);
public EventOutput createAndAttachEventOutput(){
EventOutput=新的EventOutput();
如果(添加(输出)){
int cons=connectionCounter.incrementAndGet();
log.debug(“活动连接计数:”+cons);
}
返回输出;
}
@凌驾
public void onClose(最终ChunkedOutput输出){
int cons=connectionCounter.decrementAndGet();
调试(“连接已关闭。活动连接计数:”+cons);
}
@凌驾
public void onException(最终ChunkedOutput ChunkedOutput,最终异常){
log.trace(“检测到异常”,异常);
}
public int getConnectionCount(){
返回connectionCounter.get();
}
}
@路径(“通知”)
公共静态类通知资源{
@得到
@产生(SseFeature.SERVER\u SENT\u事件)
公共事件输出订阅(){
log.debug(“新流订阅”);
EventOutput EventOutput=broadcaster.createAndAttachEventOutput();
返回事件输出;
}
}   
@凌驾
受保护的应用程序配置(){
ResourceConfig=newResourceConfig(NotificationsResource.class);
配置寄存器(SseFeature.class);
返回配置;
}
@试验
public void test()引发异常{
//检查是否没有连接
assertEquals(0,广播者.getConnectionCount());
//连接用户
日志信息(“连接用户”);
EventInput EventInput=目标(“通知”).request().get(EventInput.class);
assertFalse(eventInput.isClosed());
//现在有了联系
assertEquals(1,广播者.getConnectionCount());
//推送数据
日志信息(“广播数据”);
字符串负载=UUID.randomUUID().toString();
OutboundEvent chunk=新建OutboundEvent.Builder()
.mediaType(mediaType.TEXT\u PLAIN\u TYPE)
.姓名(“信息”)
.数据(有效载荷)
.build();
广播者。广播(块);
//读取数据
日志信息(“读取数据”);
InboundEvent InboundEvent=eventInput.read();
assertNotNull(inboundEvent);
assertEquals(有效负载,inboundEvent.readData());
//封闭订阅
log.info(“交割认购”);
eventInput.close();
assertTrue(eventInput.isClosed());
//此时,订阅服务器已断开自身连接,
//但泽西没有意识到这一点
assertEquals(1,广播者.getConnectionCount());
//等等,给TCP一个关闭连接的机会
调试(“睡眠一段时间”);
睡眠(10000);
//再次推送数据,这将真正清除未连接的客户端
log.info(“再次广播数据”);
广播者。广播(块);
睡眠(100);
//已经没有订户了
assertEquals(0,广播机.getConnectionCount());//失败!
}
}
也许
JerseyTest
不是一个好的测试方法。在一个不那么。。。临床设置,在使用JavaScript
EventSource
的情况下,我看到调用了
onClose()
,但只有在先前关闭的连接上广播消息之后

我做错了什么

为什么
SSE广播公司
没有检测到客户端关闭了连接

跟进

我发现,设计的作品被拒绝了:

根据15.4.1中SSE章节()中的Jersey文档,提到Jersey没有明确关闭连接,这是资源方法或客户端的责任


这到底是什么意思?资源是否应该强制超时并终止所有活动的和由客户端关闭的连接?

我认为最好在资源上设置超时并仅终止该连接,例如:

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

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

    @Override
    public void onClose(final ChunkedOutput<OutboundEvent> output) {
        int cons = connectionCounter.decrementAndGet();
        log.debug("A connection has been closed. Active connection count: "+ cons);
    }
@覆盖
public void onClose(最终ChunkedOutput输出){
int cons=connectionCounter.decrementAndGet();
调试(“连接已关闭。活动连接计数:”+cons);
}

在这种情况下,您不会关闭ChunkedOutput,因此它不会释放连接。这可能是问题所在吗?

在构造器的文档中,它说:

创建一个新实例。如果此构造函数由子类调用,则它假定子类存在的原因是实现
onClose(org.glassfish.jersey.server.ChunkedOutput)
onEx
public CountingSseBroadcaster(){
    super(CountingSseBroadcaster.class);
}