Использовать socket.io внутри файла экспресс-маршрутов

Я пытаюсь использовать Socket.io с Node.js и испускать сокет в логике маршрута.

У меня довольно стандартная версия Express 3 с файлом server.js, который находится на маршруте, а затем у меня есть index.js, который находится в папках маршрутов, которые экспортируют все страницы/общедоступные функции сайта. Поэтому они выглядят так:

exports.index = function (req, res) {
    res.render('index', {
        title: "Awesome page"
    });
}; 

с маршрутизацией, определенной в server.js, например:

app.get('/',routes.index);

Я предполагаю, что мне нужно создать объект socket.io в server.js, так как ему нужен объект сервера, но как я могу получить доступ к этому объекту и испускать его из функций экспорта index.js?

Ответ 1

Вы можете настроить файл маршрутов как функцию и передать объект Socket.IO при необходимости файла.

module.exports = function(io) {
  var routes = {};
  routes.index = function (req, res) {
    io.sockets.emit('payload');
    res.render('index', {
      title: "Awesome page"
    });
  };
  return routes;
};

Затем требуются такие маршруты:

var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var routes = require('./routes')(io);

Ответ 2

Теперь есть лучший способ сделать это с помощью Express 4.0.

Вы можете использовать app.set() для хранения ссылки на объект io.

Базовая конфигурация:

var app = require('express')();
var server = app.listen(process.env.PORT || 3000);
var io = require('socket.io')(server);
// next line is the money
app.set('socketio', io);

Внутренний маршрут или промежуточное ПО:

exports.foo = function(req,res){
    // now use socket.io in your routes file
    var io = req.app.get('socketio');
    io.emit('hi!');
}

Информация о app.set() и app.get() приведена ниже:

app.set(name, value)

Назначает имя настройки для значения. Вы можете хранить любое значение, которое хотите, но определенные имена могут быть использованы для настройки поведения сервера. Эти специальные имена перечислены в таблице настроек приложения.

Вызов app.set('foo', true) для логического свойства аналогичен вызову звонит app.enable('foo'). Точно так же, вызывая app.set('foo', false) для логического свойства аналогично вызову app.disable('foo').

Получить значение настройки с помощью app.get().

Источник: https://expressjs.com/en/api.html#app.set

Ответ 3

Ответ aarosil был отличным, но я столкнулся с той же проблемой, что и Виктор, с управлением клиентскими подключениями, используя этот подход. Для каждой перезагрузки на клиенте вы получите на сервере столько же повторяющихся сообщений (2 повторных = 2 дубликата, 3 = 3 дубликата и т.д.).

Развернувшись на ответе aarosil, я использовал этот подход для использования объекта сокета в файле маршрутов и управления сообщениями/управления дублирующимися сообщениями:

Внутренний файл сервера

// same as aarosil (LIFESAVER)
const app = require('express')();
const server = app.listen(process.env.PORT || 3000);
const io = require('socket.io')(server);
// next line is the money
app.set('socketio', io);

Внутри файла маршрутов

exports.foo = (req,res) => {

   let socket_id = [];
   const io = req.app.get('socketio');

   io.on('connection', socket => {
      socket_id.push(socket.id);
      if (socket_id[0] === socket.id) {
        // remove the connection listener for any subsequent 
        // connections with the same ID
        io.removeAllListeners('connection'); 
      }

      socket.on('hello message', msg => {
        console.log('just got: ', msg);
        socket.emit('chat message', 'hi from server');

      })

   });
}

Ответ 4

Неправильно использовать

global.io = require('socket.io').listen(server);

Ответ 5

Слишком позднее добавление здесь, но я хотел получить доступ к сокету в моих маршрутах и специально хотел передать сообщение после сохранения в базе данных. Я использовал ответ, предоставленный @aarosil, для установки/получения объекта io, отправил каждому клиенту свой идентификатор сокета при подключении, затем использовал идентификатор сокета в маршруте, чтобы иметь возможность использовать socket.broadcast.emit() вместо io.emit().

На сервере:

const io = require('socket.io')(server)
app.set('socketio', io)

io.on('connect', socket => {
  socket.emit('id', socket.id) // send each client their socket id
})

Я отправляю идентификатор сокета с каждым требованием, а затем могу выполнить следующие действия в своих маршрутах:

router.post('/messages', requireToken, (req, res, next) => {

  // grab the id from the request
  const socketId = req.body.message.socketId

  // get the io object ref
  const io = req.app.get('socketio') 

  // create a ref to the client socket
  const senderSocket = io.sockets.connected[socketId]

  Message.create(req.body.message)
    .then(message => {

      // in case the client was disconnected after the request was sent
      // and there no longer a socket with that id
      if (senderSocket) {

        // use broadcast.emit to message everyone except the original
        // sender of the request !!! 
        senderSocket.broadcast.emit('message broadcast', { message })
      }
      res.status(201).json({ message: message.toObject() })
    })
    .catch(next)
})

Ответ 6

module.parent.exports.server также будет работать, если вы экспортируете сервер в родительский модуль.