Express.js 4与使用Express路由器的Sockets

32

我正在尝试使用express.js 4创建一个非常简单的节点API,但我需要一些“实时”事件,因此我添加了socket.io。我对它们都相当新手,所以我可能错过了一些基础知识,但我找不到好的文档/教程。

在使用express生成器创建的应用程序中,我有类似于这样的内容,基于我读到的简单示例和项目文档。这可以正常工作,并且从客户端应用程序中,我可以发送/接收Socket事件:

var express = require('express');
var path = require('path');
var logger = require('morgan');
var api = require('./routes/api');
var app = express();
var io = require('socket.io').listen(app.listen(3000));

app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/api', api);

io.sockets.on('connection', function (socket) {
    console.log('client connect');
    socket.on('echo', function (data) {
        io.sockets.emit('message', data);
    });
});


// error handlers omitted

module.exports = app;

但我想使用来自我的API路由的sockets(在我上面“require”的./routes/api.js文件中)。例如,有人可能使用API来PUT / POST资源,我希望将其广播到连接的socket.io客户端。

我无法看出如何在express路由内部使用'io'变量或组织当前位于io.sockets.on('connection' ...函数中的代码。这是./routes/api.js文件:

var express = require('express');
var router = express.Router();
var io = ???;

router.put('/foo', function(req, res) {
    /* 
      do stuff to update the foo resource 
      ...
     */

    // now broadcast the updated foo..
    io.sockets.emit('update', foo); // how?
});

module.exports = router;
6个回答

39

一种选择是将其传递到req对象中。

app.js:

var express = require('express');
var path = require('path');
var logger = require('morgan');
var api = require('./routes/api');
var app = express();
var io = require('socket.io').listen(app.listen(3000));

app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));

io.sockets.on('connection', function (socket) {
    console.log('client connect');
    socket.on('echo', function (data) {
        io.sockets.emit('message', data);
    });
});

// Make io accessible to our router
app.use(function(req,res,next){
    req.io = io;
    next();
});

app.use('/api', api);

// error handlers omitted

module.exports = app;

./routes/api.js:

var express = require('express');
var router = express.Router();

router.put('/foo', function(req, res) {
    /* 
      do stuff to update the foo resource 
      ...
     */

    // now broadcast the updated foo..
    req.io.sockets.emit('update', foo); 
});

module.exports = router;

1
你真的让上述代码正常工作了吗?我的请求中IO在我的路由器代码中一直显示为未定义。 - atsituab
1
哎呀,我犯了一个错误 - 部分代码 // Make io accessible to our router 应该在 app.use('/api', api); 之前定义。 - platlas
1
添加一个小的2021更新,针对库的v4版本。要导入库,请使用import { Server as socketsIOServer } from 'socket.io';。要将套接字服务器连接到您的express服务器,请使用const io = new socketsIOServer(app.listen(...));。其余部分完全相同。 - ffigari

19

我稍微修改了你的文件,你能检查一下是否正常工作吗?

你可以像下面这样将你定义的io传递给你的路由:

require('./routes/api')(app,io); 

我没有测试Socket.IO部分,但没有语法错误,路由也正常工作。

server.js文件:

var express = require('express');
var app = express();
var path = require('path');
var logger = require('morgan');

var io = require('socket.io').listen(app.listen(3000));
 
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
 
io.sockets.on('connection', function (socket) {
    console.log('client connect');
    socket.on('echo', function (data) {
    io.sockets.emit('message', data);
 });
});

require('./routes/api')(app,io); 
 
console.log("Server listening at port 3000");

api.js:

module.exports = function(app,io) {
app.put('/foo', function(req, res) {
 
    /* 
 
      do stuff to update the foo resource 
 
      ...
 
     */
 
 
    // now broadcast the updated foo..
    
    console.log("PUT OK!");
 
    io.sockets.emit('update'); // how?
    res.json({result: "update sent over IO"});
 
});
}

2
谢谢Caner。这个方法可行,只需要进行一些小的调整就可以使用了,因为这个应用程序相当简单。但是,由于我正在尝试使用它来学习一些最佳实践,是否有一种方法可以继续使用express Router()呢?这似乎是express 4.x的惯用方式。 - darrend
@darrend,根据express.js文档,看起来可以这样使用http://expressjs.com/api.html,你觉得呢? - cdagli
没问题 - 我会标记为已接受并深入研究文档和代码。谢谢你的帮助。 - darrend
2
人们的更新:我找到了Logan Tegman的更简洁的解决方案 https://dev59.com/Torda4cB1Zd3GeqPISXz - Nick Pineda
嘿@cdagli,我试过了。但它显示“错误:listen EADDRINUSE :::3000”。 - HS_
从架构角度来看,将Express REST API与Websockets混合使用是否可行?这是指app.put()方法。 - Razvan Axinie

3

假设您希望从应用程序的任何位置访问SocketIO,而不仅仅是在路由器中,您可以为其创建单例。这是对我有效的方法:

//socket-singletion.js

var socket = require('socket.io');

var SocketSingleton = (function() {
  this.io = null;
  this.configure = function(server) {
    this.io = socket(server);
  }

  return this;
})();

module.exports = SocketSingleton;

然后,您需要使用您的服务器进行配置:
//server config file

var SocketSingleton = require('./socket-singleton');
var http = require('http');
var server = http.createServer(app);
SocketSingleton.configure(server); // <--here
server.listen('3000');

最后,您可以在任何地方使用它:

//router/index.js

var express = require('express');
var router = express.Router();
var SocketSingleton = require('../socket-singleton');

/* GET home page. */
router.get('/', function(req, res, next) {
  setTimeout(function(){
    SocketSingleton.io.emit('news', {msg: 'success!'});
  }, 3000);
  res.render('index', { title: 'Express' });
});

module.exports = router;

在您的解决方案中,socket-singletion.js 中的关键字在初始化时仍保持未定义状态。 - abhishake
@abhishake 你是否在使用箭头函数?如果是的话,“this”会有不同的行为,它可能无法像上面描述的那样工作。 - Edudjr

1

Edudjr的回答进行了重构。

将单例模式更改为创建一个新的socket.io服务器实例。

const { Server } = require('socket.io');
const singleton = (() => {
    this.configure = (server) => this.io = new Server(server)
    return this
})();

module.exports = singleton

初始化你的express应用程序、服务器和单例。

// initialise app
const app = express();
const server = require('http').createServer(app);

// configure socket.io
socket.configure(server)

然后在您的路由器中。
const socket = require('/utils/socket-singleton');

socket.io.emit('event', {message: 'your message here'})

1

我认为最好的方法是将io设置为req的属性,如下所示:

app.use(function(req,res,next){
    req.io = io;
    next();
});

app.use('/your-sub-link', your-router);

1
另一个选项是使用req.app

app.js

const express = require('express');
const path = require('path');
const logger = require('morgan');
const api = require('./routes/api');

const app = express();
const io = require('socket.io').listen(app.listen(3000));

// Keep the io instance
app.io = io;

app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));

// ...

app.use('/api', api);

module.exports = app;

routes/api.js

const express = require('express');
const router = express.Router();

router.put('/foo', function(req, res) {
    /*
     * API
     */

    // Broadcast the updated foo..
    req.app.io.sockets.emit('update', foo);
});

module.exports = router;

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