我认为此问题与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)中的泽西岛文档,它提到泽西岛没有明确关闭连接,它是资源方法或者资源方法的责任。客户端。
这究竟是什么意思?资源是否应该执行超时并终止所有活动和已关闭的客户端连接?
答案 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,以免它释放连接。这可能是问题吗?