在Node.js中实现服务器推送事件的简单方法?

29

我查看了一些资料,似乎在Node.js中实现SSE的方式都比较复杂,但是应该有更简单的方法来发送和接收SSE。是否有任何API或模块可以使这个过程更简单?


2
使用套接字怎么样? - Daniel
1
@Daniel,SSE运行在HTTP上,而HTTP运行在TCP套接字上 :) "Websockets"可能更合适。 然而现在有许多库可以处理SSE。我个人创建了https://www.npmjs.com/package/@toverux/expresse,因为对现有的包不满意。 - Morgan Touverey Quilling
1
我正在寻找一些简单的东西,但是找不到任何东西,所以我使用nodejs制作了一个非常简单的SSE服务器。它在https://github.com/TheSalarKhan/node-sse-server上。为了在生产中使用它,您应该添加身份验证中间件。 - Salar Khan
7个回答

69

这里有一个Express服务器,每秒钟发送一个Server-Sent Event(SSE),从10倒数到0:

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!'))

把上面的代码放入一个文件中(index.js),然后运行它:node index 接下来,将以下HTML代码放入一个文件中(public/index.html):
<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>

在您的浏览器中打开localhost:3000,并观看SSE倒计时。


3
能否请您帮帮我?我正在做和你提到的一样的事情,但我的数据总是在 res.end() 时一次性发送,即使我在每个 res.write() 后尝试了 res.flush(),但什么也没有效果。我的问题在这里:https://dev59.com/tbroa4cB1Zd3GeqPm5LU。 - Sudhanshu Gaur

17

我在这里添加了一个简单的SSE实现。它只是一个Node.js文件。

你可以在这里查看结果:https://glossy-ox.glitch.me/

const http = require('http');
const port = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
  // Server-sent events endpoint
  if (req.url === '/events') {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      ...(req.httpVersionMajor === 1 && { 'Connection': 'keep-alive' })
    });

    const refreshRate = 1000; // in milliseconds
    return setInterval(() => {
      const id = Date.now();
      const data = `Hello World ${id}`;
      const message =
        `retry: ${refreshRate}\nid:${id}\ndata: ${data}\n\n`;
      res.write(message);
    }, refreshRate);
  }

  // Client side
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end(`
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title>SSE</title>
      </head>
      <body>
        <pre id="log"></pre>
      </body>
      <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
          document.getElementById('log').innerHTML += event.data + '<br>';
        };
      </script>
    </html>
  `);
});

server.listen(port);

server.on('error', (err) => {
  console.log(err);
  process.exit(1);
});

server.on('listening', () => {
  console.log(`Listening on port ${port}`);
});

3
这是一个不错的答案,因为它没有使用任何第三方依赖 :) 但是我想指出,res.end(message) 应被替换为 setInterval(() => res.write(message), 1000),因为连接不应该被终止,应该一直使用同一连接。目前你的代码会导致客户端每秒重新连接并创建新连接。 - Karlis Bikis
1
感谢@KarlisBikis的评论。你说得完全正确。我已经将你的代码建议添加到我的帖子中。谢谢! - codeKonami
2
请注意,'Connection': 'keep-alive' 不能用于 HTTP/2。如果使用 HTTP/2,则需要在设置标头之前检查 res.httpVersionMajor。此外,您应该每隔大约 15 秒发送一个保持活动数据包(尽管您的示例每秒发送一个测试数据包)。一个空注释行 :\n 就可以了。 - ShortFuse
1
实际上,我建议将res存储在Set或Array中。如果想要向组广播事件(例如socket.io),则需要迭代并发送。在close事件中,从集合中删除它。但是这个示例没有任何依赖项,做得很好 :) - ShortFuse
1
感谢您的澄清,@ShortFuse。老实说,我还没有在生产环境中使用过SSE,但如果我这样做,您的消息将帮助我构建更加健壮的东西 :) - codeKonami
显示剩余3条评论

7
如果您正在使用Express,这是最简单的方式:https://www.npmjs.com/package/express-sse 在后端:
const SSE = require('express-sse');

const sse = new SSE();

...

app.get('/sse', sse.init);

...

sse.send('message', 'event-name');

关于前端:

const EventSource = require('eventsource');

const es = new EventSource('http://localhost:3000/sse');

es.addEventListener('event-name', function (message) {
  console.log('message:', message)
});

3

2
**client.js**

var eventSource = new EventSource("/route/events");
eventSource.addEventListner("ping", function(e){log(e.data)});

//if no events specified
eventSource.addEventListner("message", function(e){log(e.data)});

**server.js**

http.createServer((req, res)=>{

    if(req.url.indexOf("/route/events")>=){

      res.setHeader('Connection', 'keep-alive');

      res.setHeader("Cache-Control", "no-cache");

      res.setHeader("Content-Type", "text/event-stream");

      let event = "event: ping";

      let id = `id: ${Date.now()}`;

      let data = {
         message:`hello @${new Date().toString()}`
      }

      data = "data: "+JSON.stringify(data);

      res.end(`${event}\n${id}\n${data}\n\n`);
   }
}).listen(PORT)

0

在查看了其他答案后,我终于让它工作了,但最终我不得不做一些不同的事情。

[package.json] 使用express-sse

确切的express-sse版本非常重要。最新版本尝试使用res.flush(),但会失败并导致HTTP服务器崩溃。

"express-sse": "0.5.1",

[终端] 安装 express-sse:

npm install

[app.js] 使用路由器:

app.use(app.baseUri, require('./lib/server-sent-events').router);

[server-sent-events.js] 创建sse库:

调用pause()相当于flush(),这个函数已经从express中移除了。它确保你将会在消息被发送时持续接收到它们。

var express = require('express');
const SSE = require('express-sse');
const sse = new SSE();

var router = express.Router();
router.get('/sse', sse.init)
module.exports = {
    send,
    router
};


async function send(message) {
    sse.send(message.toProperCase(), 'message');
    await pause();
}

function pause() {
    return new Promise((resolve, reject) => {
       setImmediate(resolve)
    })
 }

[your-router.js] 使用 sse 库并调用 send

var express = require('express');
var serverSentEvents = require('../lib/server-sent-events');

var router = express.Router();
router.get('/somepath', yourhandler);
module.exports = router;

async function yourhandler (req, res, next) {
    await serverSentEvents.send('hello sse!'); // <<<<<
}

[your-client-side.js] 接收 SSE 更新:

我建议您保留 event.data.replace(/"/g,''),因为 express-sse 会添加引号并且我们不需要它们。

const eventSource = new EventSource('http://yourserver/sse');

eventSource.onmessage = function(event) {
    document.getElementById("result").innerHTML = event.data.replace(/"/g,'') + '...';
  };

-42

你可以使用Socket.io来实现这样的功能。首先,你需要使用npm install socket.io安装它。然后,在你的代码中,你需要有var io = require(socket.io);

你可以查看Socket.IO提供的更深入的示例

在服务器上,你可以使用类似以下的代码:

var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;

server.listen(port, function () {
  console.log('Server listening at port ' + port);
});

app.use(express.static(__dirname + '/public'));

io.on('connection', function (socket) {
    socket.emit('EVENT_NAME', {data});
});

在客户端上类似于这样:

<script src="socket_src_file_path_here"></script>
<script>
  var socket = io('http://localhost');
  socket.on('EVENT_NAME', function (data) {
    console.log(data);
    //Do whatever you want with the data on the client
  });
</script>

45
我绝对反对这种方法!我长期使用Socket.IO,可以肯定地说,随着时间的推移,你将开始面对许多困难。服务器端事件是单向的,而sockets是双向的。一个是请求-响应类型,另一个是长连接类型。差别很大,你绝对不需要sockets —— 你将不得不处理重新连接、垃圾邮件保护、服务器宕机,也许还有集群。使用简单的SSE吧,它已经在浏览器中实现了!查看其他答案! - Andrey Popov
39
OP 要求 SSE,而不是 WebSockets。 - krulik

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