Node.js SSE中的res.write()在流中不发送数据

8
我正在尝试在NodeJs中添加SSE(服务器推送事件),但当我使用res.write()发送响应时,数据不会被发送,只有在写入res.end()之后,所有数据才会同时发送。
我已经在Github、StackOverflow上找到了很多关于此问题的帖子,在这些帖子中都提到在每个res.write()之后使用res.flush(),但这对我也没有起作用,而且我没有显式地使用任何压缩模块。
请问是否有任何方法可以使其工作? 服务端代码:
const express = require('express')

const app = express()
app.use(express.static('public'))

app.get('/countdown', function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  })
  countdown(res, 10)
})

function countdown(res, count) {
  res.write("data: " + count + "\n\n")
  if (count)
    setTimeout(() => countdown(res, count-1), 1000)
  else
    res.end()
}

app.listen(3000, () => console.log('SSE app listening on port 3000!'))

客户端代码

<html>
<head>
<script>
  if (!!window.EventSource) {
    var source = new EventSource('/countdown')

    source.addEventListener('message', function(e) {
      document.getElementById('data').innerHTML = e.data
    }, false)

    source.addEventListener('open', function(e) {
      document.getElementById('state').innerHTML = "Connected"
    }, false)

    source.addEventListener('error', function(e) {
      const id_state = document.getElementById('state')
      if (e.eventPhase == EventSource.CLOSED)
        source.close()
      if (e.target.readyState == EventSource.CLOSED) {
        id_state.innerHTML = "Disconnected"
      }
      else if (e.target.readyState == EventSource.CONNECTING) {
        id_state.innerHTML = "Connecting..."
      }
    }, false)
  } else {
    console.log("Your browser doesn't support SSE")
  }
</script>
</head>
<body>
  <h1>SSE: <span id="state"></span></h1>
  <h3>Data: <span id="data"></span></h3>
</body>
</html>
解决方案 - 我使用了 nginx 作为反向代理,所以出现了问题。我尝试了这个解决方案,它起作用了 :)

通过 Nginx 发送 EventSource/服务器推送事件

3个回答

7
如果您的Express服务器位于防火墙或代理服务器后面,它们通常会等待服务器关闭连接后再发送完整的响应。相反,您需要在浏览器和服务器之间建立一种连接,允许使用'Connection': 'keep-alive'

2
它正常工作了,天啊,为什么我没想到呢,非常感谢你,真的非常感谢。终于,在经过3天的努力后,我找到了解决方案,干杯 :) - Sudhanshu Gaur

2
在我的情况下,问题是一个压缩中间件。在移除针对任何SSE请求的压缩中间件之后,它再次正常工作。

-3
在您的示例中,为了防止完成整个迭代以发送事件,您需要在res.write()之后立即添加res.end()。这样每个事件都会在1秒钟后发送。
function countdown(res, count) {
  res.write("data: " + count + "\n\n")
  res.end() // <-- add after each event
  if (count)
    setTimeout(() => countdown(res, count-1), 1000)
  else
    res.end()
}

我遇到了 Error [ERR_STREAM_WRITE_AFTER_END]: write after end 的错误。 - kyw
1
啊,只需要添加这个就可以了:res.writeHead(200, {'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive'})。现在我可以在我的Fetch中看到所有的res.write()输出了。 - kyw
1
你只能在最后一次结束连接,并且必须在程序的最后完成。 - Bartosz Rosa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接