在服务器关闭连接之前,浏览器不会响应服务器发送的事件

Browsers are not responding to Server-Sent Events until server closes connection

本文关键字:服务器 响应 事件 连接 浏览器      更新时间:2023-09-26

在Express/Node服务器上,我将内容类型设置为text/event-stream:

res.writeHead(200, {
  'Content-Type': 'text/event-stream'
});

然后,随着一系列回调的启动,我将数据消息写入流,并在流后面添加两行:

res.write('data: ' + JSON.stringify(data) + ''n'n');

如果我在服务器端添加日志记录,或者如果我只是用curl点击URL,我可以看到数据消息在几秒钟内被写入。

然而,当我尝试在网页中使用这些数据消息时,什么也没发生。(我正在Mac上测试Chrome、Firefox和Safari。)以下是网页的样子:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Testing</title>
</head>
<body>
  <h1>Server-Sent Events Test</h1>
  <script>
    var source = new EventSource('/library/search?q=medicine&async');
    source.onmessage = function(e) {
      document.body.innerHTML += JSON.parse(e.data).name + '<br>';
    };
  </script>
</body>
</html>

如果我在服务器端添加最后一个回调来关闭连接(使用res.end()),那么浏览器会同时响应所有数据消息,并且只响应res.end()一次。这似乎违背了使用服务器发送事件的目的。

我需要更改什么(除了放弃并切换到XHR轮询)才能让浏览器在服务器发送事件到达时对其做出响应(这似乎正是服务器发送事件的用途和用例)?

(演示问题的测试页面可用,但现在这个问题已经解决,我已经将其删除。)

看起来您有一些正在进行压缩的中间件;在这样做的过程中,它是缓冲的,直到您完成响应。你可以通过卷曲看到这一点:

首先,裸GET:

curl <url>

接下来,添加一个Accept-Encoding标头(类似于您的浏览器正在使用的标头):

curl <url>  -H 'Accept-Encoding: gzip,deflate,sdch' --compressed

请注意,--compressed只是告诉curl为您解压缩它。

您会注意到,您在第一个上看到了预期的行为,但在第二个上看不到。这表明它与压缩有关。我建议关闭该路由的压缩,或者找到一个知道如何压缩每一帧的智能中间件。

它在Chrome中运行良好。这是我的测试代码:

sse.js:

var app = require('express')();
app.get('/', function(req, res) {
  res.sendFile(__dirname + '/sse.htm');
});
app.get('/events', function(req, res) {
  var counter = 0;
  res.writeHead(200, { 'Content-Type': 'text/event-stream' });
  setInterval(function() {
    res.write('data: ' + JSON.stringify({name: 'foo' + (counter++) }) + ''n'n');
  }, 1000);
  res.write('data: ' + JSON.stringify({name: 'foo' + (counter++) }) + ''n'n');
});
app.listen(8000);

sse.htm:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>
  <h1>Server-Sent Events Test</h1>
  <script>
    var source = new EventSource('/events');
    source.onmessage = function(e) {
      document.body.innerHTML += JSON.parse(e.data).name + '<br />';
    };
    source.onerror = function(e) {
      source.close();
    };
  </script>
</body>
</html>

这会产生以下输出:

foo0//一秒钟后foo1//两秒钟后foo2//三秒后foo3//等等。