我正在尝试实现一个服务器发送事件(SSE)网页,它是由Spring驱动的。我的测试代码执行以下操作:
浏览器使用EventSource(url)连接到服务器。Spring接受带有以下控制器代码的请求:
@RequestMapping(value="myurl", method = RequestMethod.GET, produces = "text/event-stream")
@ResponseBody
public DeferredResult<String> subscribe() throws Exception {
final DeferredResult<String> deferredResult = new DeferredResult<>();
resultList.add(deferredResult);
deferredResult.onCompletion(() -> {
logTimer.info("deferedResult "+deferredResult+" completion");
resultList.remove(deferredResult);
});
return deferredResult;
}
所以它主要是将DeferredResult放在一个列表中,并注册一个完成回调,这样我就可以在完成时从列表中删除这个东西。
现在我有了一个timer方法,它将通过所有注册的“浏览器”的延迟结果定期输出当前时间戳。
@Scheduled(fixedRate=10000)
public void processQueues() {
Date d = new Date();
log.info("outputting to "+ LoginController.resultList.size()+ " connections");
LoginController.resultList.forEach(deferredResult -> deferredResult.setResult("data: "+d.getTime()+"\n\n"));
}
数据被发送到浏览器,以下客户端代码正常工作:
var source = new EventSource('/myurl');
source.addEventListener('message', function (e) {
console.log(e.data);
$("#content").append(e.data).append("<br>");
});
现在的问题是:
对于计时器线程中的每个setResult()调用,都会调用DeferredResult上的完成回调。因此,由于某种原因,连接在setResult()调用之后关闭。浏览器中的SSE按照规范重新连接,然后再次连接。所以在客户端我有一个轮询行为,但是我想要一个保持打开的请求,在那里我可以一次又一次地在相同的延迟结果上推送数据。
我错过了什么吗?DeferredResult不能发送多个结果吗?我在计时器线程中设置了10秒的延迟,以查看请求是否仅在setResult()之后终止。因此在浏览器中,请求一直保持打开状态,直到计时器按下数据,然后关闭。
谢谢你的提示。另一个注意事项:我向tomcat中的所有过滤器/servlet添加了async支持。
最佳答案
实际上,DeferredResult只能设置一次(注意,setResult返回一个布尔值)。它使用全范围的Spring MVC处理选项来完成处理,也就是说,除了异步生成的返回值之外,您对Spring MVC请求期间发生的一切所知或多或少都是相同的。
SSE需要的是更集中的东西,即使用HttpMessageConverter将每个值写入响应。我已经创建了一张https://jira.spring.io/browse/SPR-12212的罚单。
请注意,Spring的SockJS支持确实有一个SSE传输,它处理一些额外的事务,比如跨域请求和cookie(对IE很重要)。它还用于WebSocket API和WebSocket风格的消息传递(即使WebSocket在客户端或服务器端都不可用),它完全抽象了HTTP长轮询的细节。
作为解决方法,您还可以使用HttpMessageConverter直接写入Servlet响应。
关于html5 - 为什么DeferredResult在尝试使用SSE时以setResult()结尾,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25880456/