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