每当在Node.js中请求一个API端点时,发送一个服务器推送事件(SSE)。

4
我将尝试在我的NodeJs服务器被访问时实现发送服务器推送事件(SSE)的方法。以下是我实现的方式:
//...code...
import EventSource from "eventsource";

const EventEmitter = require('events');
const events = new EventEmitter();
// events.setMaxListeners(10);


// api end point route from where the new order requests will be coming
router.route('/new-order')
  .post((req, res) => {
    const orderData = req.body.data;
    //... save order and emit an event to send response to the client react app
    events.emit('newOrder', orderData);
  });

// define a function to send SSE events to the client
const sse = res => data => {
  const dataToSend = JSON.stringify(data);
  res.write(`data:${dataToSend}`);
  res.write("\n\n");
};

// define an EventSource route for the client to be connected for new events
router.route('/sse')
  .get((req, res) => {

    res.writeHead(200, {
      "Connection": "keep-alive",
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
    });

    res.write("\n");

    const sendSSE = sse(res);
    /*
    PROBLEMATIC CODE-- EACH request to /sse route adds new event listener,
    and in minutes, it exceeds the limit of maxListeners, and I get the warning 
    to increase the max event listeners limit by using setMaxListeners()
    even if only 1 user is using the front end app.
    */
    events.on('newOrder', sendSSE);
  });

//...Code...


// Client Side
const newOrderReceived = (e) => {
  console.log(JSON.parse(e.data));
};

if ( !window.eventSource ) {
  window.eventSource = new EventSource('https://example.com/sse');
  window.eventSource.onmessage = newOrderReceived;
}

但问题在于,即使只有1个用户使用应用程序,maxListeners也会非常快地耗尽。

如果我将事件绑定代码更改为

events.once('newOrder', sendSSE);

事件的maxListeners错误消失了,但在第一个顺序之后它没有通知我的应用程序。 我找不到绑定事件的方法,在我的 /路由之外,因为我需要通过

发送数据。
res.write(`data:${dataToSend}`);
res.write("\n\n");

在我的情况下,res对象仅在路由中可用,即/sse

有什么解决办法或更好的方法可用于在NodeJS中发送服务器推送事件(SSE)以通知我的前端应用程序,在每次请求我的API端点时进行通知吗?

任何帮助都将不胜感激。

附言: 我搜索这个问题时看到的所有教程/指南都在路由内部实现了一个setInterval来发送数据,我没有找到一篇教程解释如何响应服务器上的事件发送数据。


我现在也遇到了完全相同的问题。你找到获取res对象的方法了吗? - Priyath Gregory
@fsociety,我已经添加了解决这个问题的方案,你可以在下面检查答案。谢谢。 - Abid Ali
1个回答

2
这是我解决这个问题的方法。
// Events.js File
const EventEmitter = require('events');
const events = new EventEmitter();
events.setMaxListeners(parseInt(config.MAX_EVENT_LISTENERS));

events.on('error', (err) => {
  // handle Error
});

// event to fire on each new order.
events.on('newOrder', (data) => {
  // and in the events.js file, I access it from the global scope of Node
  // so this is the `res` object from the route where I want to send SSE.
  if ( global.sseResponse ) {
    const sendSSE = sse(global.sseResponse);
    sendSSE(data);
  } else {
    // no sse listener, do something else,
  }
});

module.exports = events;

在我的路由文件中。
// My routes file where I want to use this
router.route('/sse')
  .get(
    (req, res) => {

      res.writeHead(200, {
        "Connection": "keep-alive",
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
      });

      res.write("\n");
      // Node has a global variable as browsers have a window variable, I attached the res object of my route to
      // the Node's global object to access it outside of this route
      global.sseResponse = res;
      const sendSSE = ApiController.sse(res);
      // keep the connection on
      setInterval(() => {
        sendSSE({ message: 'keep-connection-alive' });
      }, 5000);
    });

这是我正在使用的发送SSE的函数,您可以在任何地方编写它并从那里导出以在多个位置使用

// function to send server sent events (sse)
const sse = res => data => {
  const dataToSend = JSON.stringify(data);

  res.write(`data:${dataToSend}`);
  res.write("\n\n");

  // this is the important part if using the compression npm module
  res.flush();
},

使用这种方法解决了我的问题,希望它也能帮助其他人。


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