使用Express发送事件与express

时间:2018-01-06 20:11:10

标签: javascript node.js express server-sent-events

我会尝试尽可能简化这一点,因此我不必发布大量代码。下面是我的应用程序现在做的事情:

  • 用户从浏览器上传音频文件
  • 该文件在我的服务器上处理,此过程需要一些时间,大约需要8个步骤才能完成。
  • 一切都完成后,用户会在浏览器中获得该过程完成的反馈。

我想要添加的内容是,在完成的过程中的每个步骤之后,将一些数据发送回服务器。例如:"您的文件已上传","元数据处理","图像提取"等等,以便用户获得有关正在发生的事情的增量反馈,我相信Server Sent Events可以帮助我做到这一点。

目前,该文件已通过app.post('/api/track', upload.single('track'), audio.process)发布到服务器。 audio.process是所有魔法发生的地方,并使用res.send()将数据发送回浏览器。非常典型。

在尝试使事件正常工作时,我已经实现了这个功能

app.get('/stream', function(req, res) {
  res.sseSetup()

  for (var i = 0; i < 5; i++) {
    res.sseSend({count: i})
  }
})

当用户从服务器上传文件时,我只是调用此路由并在客户端注册所有必要的事件:

progress : () => {
if (!!window.EventSource) {
  const source = new EventSource('/stream')

  source.addEventListener('message', function(e) {
    let data = JSON.parse(e.data)
    console.log(e);
  }, false)

  source.addEventListener('open', function(e) {
    console.log("Connected to /stream");
  }, false)

  source.addEventListener('error', function(e) {
    if (e.target.readyState == EventSource.CLOSED) {
      console.log("Disconnected from /stream");
    } else if (e.target.readyState == EventSource.CONNECTING) {
      console.log('Connecting to /stream');
    }
  }, false)
} else {
  console.log("Your browser doesn't support SSE")
}
}

这可以正常工作,当我上传一首曲目时,我会得到一个从0到4开始的事件流。太棒了!

我的问题/问题:如何从audio.process路由向/stream路由发送相关消息,以便消息可以与发生的事件相关。 audio.process必须是POST/stream必须是GET,标题为'Content-Type': 'text/event-stream'。在GET内提出audio.process请求似乎有点奇怪,但这是最好的方法吗?

任何和所有建议/提示表示赞赏!如果您需要更多信息,请与我们联系。

1 个答案:

答案 0 :(得分:4)

新答案:

只需使用socket.io,它就更容易也更好! https://www.npmjs.com/package/socket.io#in-conjunction-with-express

基本设置:

const express = require('express');
const PORT = process.env.PORT || 5000;
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
// listen to socket connections
io.on('connection', function(socket){
  // get that socket and listen to events
  socket.on('chat message', function(msg){
    // emit data from the server
    io.emit('chat message', msg);
  });
});
// Tip: add the `io` reference to the request object through a middleware like so:
app.use(function(request, response, next){
  request.io = io;
  next();
});
server.listen(PORT);
console.log(`Listening on port ${PORT}...`);

并且在任何路由处理程序中,您都可以使用socket.io:

app.post('/post/:post_id/like/:user_id', function likePost(request, response) {
  //...
  request.io.emit('action', 'user liked your post');
})

客户方:

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  $(function () {
    var socket = io();
    $('form').submit(function(e){
      e.preventDefault(); // prevents page reloading
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
    socket.on('chat message', function(msg){
      $('#messages').append($('<li>').text(msg));
    });
  });
</script>

完整示例:https://socket.io/get-started/chat/

原始答案

某人(用户:https://stackoverflow.com/users/451634/benny-neugebauer |来自本文:addEventListener on custom object)字面上给了我一个关于如何实现这一点的提示,除了快递之外没有任何其他包!我有它的工作!

首先,导入Node的EventEmitter:

const EventEmitter = require('events');

然后创建一个实例:

const Stream = new EventEmitter();

然后为事件流创建一个GET路由:

app.get('/stream', function(request, response){
  response.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  Stream.on("push", function(event, data) {
    response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
  });
});

在这个GET路由中,你写的是请求是200 OK,content-type是text / event-stream,没有缓存,还有keep-alive。

您还将调用EventEmitter实例的.on方法,该方法需要2个参数:要侦听的事件的字符串和处理该事件的函数(该函数可以使用尽可能多的参数)

现在....发送服务器事件所需要做的就是调用EventEmitter实例的.emit方法:

Stream.emit("push", "test", { msg: "admit one" });

第一个参数是您要触发的事件的字符串(确保它与GET路由中的事件相同)。 .emit方法的每个后续参数都将传递给侦听器的回调!

就是这样!

由于您的实例是在路由定义上方的范围内定义的,因此您可以从任何其他路径调用.emit方法:

app.get('/', function(request, response){
  Stream.emit("push", "test", { msg: "admit one" });
  response.render("welcome.html", {});
});

感谢JavaScript作用域的工作原理,您甚至可以将EventEmitter实例传递给其他函数,甚至可以从其他模块传递:

const someModule = require('./someModule');

app.get('/', function(request, response){
  someModule.someMethod(request, Stream)
  .then(obj => { return response.json({}) });
});

在someModule中:

function someMethod(request, Stream) { 
  return new Promise((resolve, reject) => { 
    Stream.emit("push", "test", { data: 'some data' });
    return resolve();
  }) 
}

那很简单!不需要其他包裹!

以下是Node的EventEmitter类的链接:https://nodejs.org/api/events.html#events_class_eventemitter

我的例子:

const EventEmitter = require('events');
const express = require('express');
const app = express();

const Stream = new EventEmitter(); // my event emitter instance

app.get('/stream', function(request, response){
  response.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  Stream.on("push", function(event, data) {
    response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
  });
});

setInterval(function(){
  Stream.emit("push", "test", { msg: "admit one" });
}, 10000)

<强>更新

我创建了一个更易于使用且不会导致内存泄漏的模块/文件!

const Stream = function() {
  var self = this;

  // object literal of connections; IP addresses as the key
  self.connections = {};

  self.enable = function() {
    return function(req, res, next) {
      res.sseSetup = function() {
        res.writeHead(200, {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
          'Connection': 'keep-alive'
        })
      }

      res.sseSend = function(id, event, data) {
        var stream = "id: " + String(id) + "\n" +
        "event: " + String(event) + "\n" +
        "data: " + JSON.stringify(data) +
        "\n\n";

        // console.log(id, event, data, stream);

        res.write(stream);
      }

      next()
    }
  }

  self.add = function(request, response) {
    response.sseSetup();
    var ip = String(request.ip);
    self.connections[ip] = response;
  }.bind(self);

  self.push_sse = function(id, type, obj) {
    Object.keys(self.connections).forEach(function(key){
      self.connections[key].sseSend(id, type, obj);
    });
  }.bind(self);

}

/*
  Usage:
  ---
  const express = require('express');
  const Stream = require('./express-eventstream');
  const app = express();
  const stream = new Stream();
  app.use(stream.enable());
  app.get('/stream', function(request, response) {
    stream.add(request, response);
    stream.push_sse(1, "opened", { msg: 'connection opened!' });
  });
  app.get('/test_route', function(request, response){
    stream.push_sse(2, "new_event", { event: true });
    return response.json({ msg: 'admit one' });
  });
*/

module.exports = Stream;

脚本位于此处 - https://github.com/ryanwaite28/script-store/blob/master/js/express-eventstream.js