javascript - Slanje websocket poruke iz ruta


Pokušavam postaviti svoj server s websockets tako da kada nešto ažuriram putem svojih ruta, također mogu poslati websocket poruku kada se nešto na toj ruti ažurira.

Ideja je spremiti nešto u moj Mongo db kada netko posjeti rutu /add-team-member, na primjer, a zatim poslati poruku svima koji su povezani putem websocket-a i koji su dio bilo koje websocket sobe koja odgovara tom timu.

Slijedio sam dokumentaciju za socket.io kako bih postavio svoju aplikaciju na sljedeći način:

App.js

// there's a lot of code in here which sets what to use on my app but here's the important lines

const app = express();
const routes = require('./routes/index');

const sessionObj = {
    secret: process.env.SECRET,
    key: process.env.KEY,
    resave: false,
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
             secret : 'test',
             cookie:{_expires : Number(process.env.COOKIETIME)}, // time im ms    
}

app.use(session(sessionObj));
app.use(passport.initialize());
app.use(passport.session());

module.exports = {app,sessionObj};

start.js

const mongoose = require('mongoose');
const passportSocketIo = require("passport.socketio");
const cookieParser = require('cookie-parser');

// import environmental variables from our variables.env file
require('dotenv').config({ path: 'variables.env' });

// Connect to our Database and handle an bad connections
mongoose.connect(process.env.DATABASE);

// import mongo db models
require('./models/user');
require('./models/team');

// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);

const server = app.app.listen(app.app.get('port'), () => {
  console.log(`Express running → PORT ${server.address().port}`);
});

const io = require('socket.io')(server);

io.set('authorization', passportSocketIo.authorize({
  cookieParser: cookieParser,
  key:         app.sessionObj.key,       // the name of the cookie where express/connect stores its session_id 
  secret:      app.sessionObj.secret,    // the session_secret to parse the cookie 
  store:       app.sessionObj.store,        // we NEED to use a sessionstore. no memorystore please 
  success:     onAuthorizeSuccess,  // *optional* callback on success - read more below 
  fail:        onAuthorizeFail,     // *optional* callback on fail/error - read more below 
}));


function onAuthorizeSuccess(data, accept){}

function onAuthorizeFail(data, message, error, accept){}

io.on('connection', function(client) {  
  client.on('join', function(data) {
      client.emit('messages',"server socket response!!");
  });

  client.on('getmessage', function(data) {
    client.emit('messages',data);
});  

});

Moj problem je što imam puno Mongo DB akcija spremanja koje se odvijaju u mojoj ./routes/index datoteci i želio bih moći slati poruke iz mojih ruta, a ne s kraja start.js gdje je socket.io povezan.

Postoji li neki način da mogu poslati WebSocket poruku iz svoje ./routes/index datoteke iako je IO postavljen kasnije u datoteci start.js?

na primjer nešto ovako:

router.get('/add-team-member', (req, res) => {
  // some io.emit action here
});

Možda trebam premjestiti mjesto na kojem inicijaliziram socket.io stvari, ali nisam uspio pronaći nikakvu dokumentaciju o tome ili možda već nekako mogu pristupiti socket.io iz ruta?

Hvala i cijenim pomoć, javi mi ako nešto nije jasno!

Odgovor #1

Kao što je gore spomenuto, io je u vašem globalnom opsegu. Ako to učinite

router.get('/add-team-member', (req, res) => {
    io.sockets.emit('AddTeamMember');
});

Tada će svaki klijent koji je povezan, ako sluša taj događaj AddTeamMember, izvršiti svoju pridruženu .on funkciju na svojim odgovarajućim klijentima. Ovo je vjerojatno najlakše rješenje, i osim ako ne očekujete ogroman val korisnika bez ikakvih planova za balansiranje opterećenja, ovo bi trebalo biti prikladno za sada.

Još jedna alternativa koju možete isprobati: socket.io biblioteka ima funkcionalnost soba gdje se možete pridružiti i emitirati koristeći sam io objekt https://socket.io/docs/rooms-and-namespaces/ ako imate afinitet za ovo, izgledalo bi otprilike ovako:

io.sockets.in('yourroom').broadcast('AddTeamMember');

Ovo bi zapravo radilo istu stvar kao i gore, samo što bi umjesto da šalje signal svim klijentima, slalo samo onima koji su ekskluzivni za tu sobu. U osnovi bi trebao smisliti način da dobiješ korisnikov socket u sobu //prije// nego što izvrši get zahtjev, drugim riječima, da ih učiniš ekskluzivnima. Na taj način možeš smanjiti količinu opterećenja koju tvoj server mora izbacivati svaki put kada se taj zahtjev za rutu izvrši.

Na kraju, ako vam nijedna od gore navedenih opcija ne odgovara, i ako jednostavno morate poslati tom pojedinom klijentu kada on to inicira, onda će situacija postati komplicirana, jer morate imati neku vrstu identifikatora za tu osobu, a budući da nemate referencu, morali biste pohraniti sve svoje sokete prilikom povezivanja, a zatim napraviti usporedbu. Ne preporučujem u potpunosti nešto ovakvo, jer, pa, nikada to nisam testirao i ne znam kakve bi posljedice mogle nastati, ali evo sažetak ideje koju sam imao:

app.set('trust proxy', true)
var SOCKETS = []
io.on('connection', function(client) {
  SOCKETS.push(client);
  client.on('join', function(data) {
    client.emit('messages',"server socket response!!");
  });

  client.on('getmessage', function(data) {
    client.emit('messages',data);
  });
});

router.get('/add-team-member', (req, res) => {
    for (let i=0; i< SOCKETS.length; i++){
        if(SOCKETS[i].request.connection.remoteAddress == req.ip)
          SOCKETS[i].emit('AddTeamMember');
    }
});

Imajte na umu, ako krenete tim putem, morat ćete održavati taj redoslijed kada se korisnici odspojaju, i ako upravljate sesijama, to će vrlo brzo postati prilično komplicirano.

Sretno, javi nam svoje rezultate.

Odgovor #2

Da, to je moguće, samo morate pridružiti instancu socket.io sve dok vaš server prima zahtjev. Gledajući vaš fajl start.js, samo trebate zamijeniti vaše funkcije kao:

// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const io = require('socket.io')(app.app);

const server = app.app.listen(app.app.get('port'), () => {
server.on('request', function(request, response){
    request.io = io;
}
console.log(`Express running → PORT ${server.address().port}`);
});

Sada, kada primiš događaj za koji želiš poslati neku poruku klijentima, možeš koristiti svoju io instancu iz objekta zahtjeva.

router.get('/add-team-member', (req, res) => {
    req.io.sockets.emit('addteammember', {member: 6});
    //as you are doing a broadcast you just need broadcast msg
    ....
    res.status(200)
    res.end()
});

Time sam također mogao integrirati s testnim okvirom poput Mochae i testirati i događaje koji su emitirani...

Napravio sam neka integriranja poput toga, i prema mom iskustvu, posljednja stvar koju treba učiniti bila je poslati poruku instancama preko socketa.

Kao dobru praksu, na samom početku middleware funkcija koje sam imao radila je provjera valjanosti podataka, sanitizacija podataka i čišćenje podataka. Evo mog radnog primjera:

var app = require('../app');
var server = require('http').Server(app);
var io = require('socket.io')(server);

io.on('connection', function(client) {
        client.emit('connected');
        client.on('disconnect', function() {
            console.log('disconnected', client.id);
        });
});

server.on('request', function(request, response) {
    request.io = io;
});

pg.initialize(app.config.DATABASEURL, function(err){
  if(err){
    throw err;
  }

  app.set('port', process.env.PORT || 3000);

    var server1 = server.listen(app.get('port'), function(){
    var host = 'localhost';
    var port = server1.address().port;

    console.log('Example app listening at http://%s:%s', host, port);
  });
});