为什么我的服务器发送了作为批处理的事件?

时间:2018-06-15 07:10:47

标签: javascript java spring server-sent-events

我有一个基于Java 8 / Spring4的Web应用程序,它使用服务器发送事件(SSE)向运行某些Javascript并更新进度条的基于浏览器的客户端报告长时间运行的进程的进度。在我的开发环境和开发服务器上,SSE几乎实时到达客户端。我可以看到他们使用Chrome开发工具到达(连同他们的时间戳),并且进度条可以顺利更新。

但是,当我部署到我们的生产环境时,我会观察到不同的行为。在长时间运行的进程完成之前,事件不会到达浏览器。然后它们全部到达(根据开发工具,这些事件的时间戳都在几百毫秒之内)。进度条在持续时间内停留在0%,然后非常快速地跳到100%。同时,我的服务器日志告诉我事件是定期生成和发送的。

以下是相关的服务器端代码:

public class LongRunningProcess extends Thread {
    private SseEmitter emitter;
    public LongRunningProcess(SseEmitter emitter) {
        this.emitter = emitter;
    }
    public void run() {
        ...
        // Sample event, representing 10% progress
        SseEventBuilder event = SseEmitter.event();
        event.name("progress");
        event.data("{ \"progress\": 10 }"); // Hand-coded JSON
        emitter.send(event);
        ...
    }
}

@RestController
public class UploadController {
    @GetMapping("/start")
    public SseEmitter start() {
        SseEmitter emitter = new SseEmitter();
        LongRunningProcess process = new LongRunningProcess(emitter);
        process.start();
        return emitter;
    }
}

以下是相关的客户端Javascript:

EventSource src = new EventSource("https://www.example.com/app/start");
src.addEventListener('progress', function(event) {
    // Process event.data and update progress bar accordingly
});

我相信我的代码非常典型,在DEV中运行得很好。但是,如果有人能看到问题,请告诉我。

该问题可能与我们的生产服务器的配置有关。 DEV和PROD都运行相同版本的Tomcat。但是,其中一些是通过负载平衡器(F5 in out case)访问的。几乎所有人都支持CDN(在我们的案例中是Akamai)。这个设置的某些部分是否会导致SSE被缓冲(或排队或缓存),这可能会产生我所看到的内容?

跟进基础架构配置的想法,我在响应头中观察到以下内容。在开发环境中,我的浏览器会收到:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: Keep-Alive
Content-Type: text/event-stream;charset=UTF-8
Keep-Alive: timeout=15, max=99
Pragma: no-cache
Server: Apache
Transfer-Encoding: chunked
Via: 1.1 example.com

这是我对事件流的期望。未知内容长度的分块响应。在生产环境中,我的浏览器会收到不同的内容:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Type: text/event-stream;charset=UTF-8
Content-Encoding: gzip
Content-Length: 318
Pragma: no-cache
Vary: Accept-Encoding

此处返回的内容具有已知长度并已压缩。我不认为这应该发生在事件流中。似乎有些东西正在将我的事件流转换为单个文件。关于如何弄清楚这是做什么的任何想法?

3 个答案:

答案 0 :(得分:1)

进行了大量调查,以确定问题的原因是网络路径中的元素。因此,以上代码正确且使用安全。如果找到SSE缓冲,则很可能要检查关键网络元素的配置。

就我而言,是Akamai作为我们的CDN,并使用F5设备作为负载均衡器。实际上,事实是两者都可以引入缓冲,这使得诊断问题变得非常困难。

Akamai Edge服务器默认情况下缓冲事件流。可以通过使用Akamai的advanced metadata来禁用它,并可以通过custom behaviours对其进行控制。目前,这无法通过Amakai的门户网站直接控制,因此您将需要让他们的工程师为您完成一些工作。

F5设备似乎默认也可以缓冲响应数据。幸运的是,这很容易更改,可以通过设备的配置门户网站自行完成。对于有问题的虚拟设备,请转到Profile : Services : HTTP,然后将 Response Chunking 的配置更改为 Preserve (在我们的示例中,它默认为 Selective < / em>)。

一旦进行了这些更改,我便开始从PROD服务器(而不仅仅是DEV服务器)几乎实时地接收SSE。

答案 1 :(得分:0)

您是否尝试过其他浏览器?我正在尝试调试类似的问题,其中SSE在iPhone客户端上有效,而在MacOS / Safari或Firefox上无效。

可能存在针对您问题的解决方法-如果服务器发送“连接:关闭”而不是保持活动状态,或者甚至关闭了连接本身,则客户端应在几秒钟后重新连接,服务器将发送当前的进度条事件。

我猜测关闭连接会刷新导致问题的任何缓冲区。

答案 2 :(得分:0)

这不能完全解决这个问题,但是与SSE,Spring和压缩的使用有关。

就我而言,我在Spring应用程序中配置了ziplet CompressionFilter,它正在关闭Http响应并导致SSE失败。这似乎与an open issue in the ziplet project有关。我禁用了过滤器,并在application.properties(server.compression.enabled = true)中启用了Tomcat压缩,它解决了SSE问题。

请注意,我没有更改默认的compressionMinSize设置,这可能与SSE通信没有得到压缩和通过有关。