javascript - NodeJS & Socket.IO: Udsend en forespørgselsbegivenhed og få svaret, hvornår/hvor skal jeg binde lytteren?


Jeg undrer mig i øjeblikket over, hvad der kunne være den bedste programmeringspraksis i dette tilfælde:

Lad os sige, at jeg har en klient forbundet til min server. Og denne klient spørger serveren om autentificering med en auth-begivenhed og hans brugernavn.

socket = io();
socket.emit('auth', "John");

I dette enkle tilfælde svarer serveren med en auth_succeed begivenhed med brugerens id.

io.on('connection', function(socket) {
    socket.on('auth', function(username) {
        socket.emit('auth_succeed', id);
    }
}

Så mit spørgsmål er, hvornår eller hvor skal jeg binde lytteren for auth_succeed-begivenheden i klienten? Der er to måder for mig:

Før udsendelse, hvilket sikrer, gætter jeg på, at responshændelsen altid vil blive håndteret korrekt, men fører til noget spaghetti-kode. F.eks:

socket = io();
socket.on('auth_succeed', function(id){
    //Do some post-auth stuff here
}); 
socket.emit('auth', "John");

Eller efter udsendelse, hvilket fører til renere kode, men kunne muligvis, tror jeg igen, misse begivenheden, hvis den sendes hurtigt nok. F.eks:

socket = io();
socket.emit('auth', "John");
socket.on('auth_succeed', function(id){
    //Do some post-auth stuff here
}); 

Hvad er dine tanker om det problem?

Svar #1

Da svaret fra emitten skal være asynkront, og klient-side JS er synkron af natur, vil socket.on('auth_succeed' bindingen ske før callbacken fra auth begivenheden.

Følgende flow vil ske på klienten...

// EXECUTION SCOPE BEGINS
...
// this will send a message to the server
// the message and/or response will be sent asynchronously
socket.emit('auth', 'John'); 
// so your code will continue before you get anything from the server

//which means the following binding will happen before any response to the above emit
socket.on('auth_succeed', function(id){
  //... handle message from server ...
});
...
// EXECUTION SCOPE ENDS

Noget tid efter, at den indhyllede omfang/funktion er færdig med at køre, vil begivenheden 'auth_succeed' blive udløst.

Du vil måske også overveje at opdele dine hændelseshåndterere...

socket.on('auth_succeed', onAuthSucceed.bind(null, socket));
socket.emit('auth', 'john');

// ... elsewhere ...

function onAuthSucceed(socket, id) {
  // handle message from server
}

Dette vil reducere støjen med din binding og dit signalbegivenhed, uanset om du vælger at binde eller udsende først.

Ved at få funktionen til at kræve alt, den har brug for, og ved at bruge binding til begivenheden, kan den pågældende metode være i en separat fil/modul og lettere testes isoleret.

Svar #2

Socket.io-dokumentationen, i Migrating from 0.9, gør et fremragende stykke arbejde med at vise, hvordan man autentificerer sockets i 1.0.

Et eksempel for dig ville være:

Du bør oprette en autentificeringsmiddleware for at håndtere dette.

Opret en controller kaldet authorization.js:

/**
 * Socket.io middleware that that authorizes clients
 * @param {object} socket
 * @param {function} next
 */
module.exports = function(socket, next) {

    // do some custom auth here
    var handshakeData = socket.request;

    // make sure the handshake data looks good with some
    // custom logic
    // if doesn't you can do this:
    // next(new Error('not authorized');
    // else just call next

    next();
}

I index.js skal du blot tilføje middleware med io.use:

var authorizeSockets = require('./controllers/authorization');
io.use(authorizeSockets);

Brug af io.set er blevet udfaset til fordel for io.use() siden socket.io 1.0.

Du kan derefter lade klienten vide, at de har autoriseret og oprettet forbindelse succesfuldt ved at udsende auth_suceed på forbindelse-begivenheden, fordi med den nye middleware på plads, vil en socket ikke kunne oprette forbindelse, medmindre de autoriserer succesfuldt.

io.on('connection', function(socket){
    socket.emit('auth_succeed', yourPayloadHere);
});