Undying
Undying

Reputation: 141

How To Prevent Multiple Socket.io Event Listeners

I need to be able to access req from my socket.io event listeners. So I did this:

Server

var express = require('express'),
    app = express(),

app.set('view engine', 'pug');

app.use(express.static(__dirname + '/public'));

client = require('socket.io').listen(8080).sockets;

app.get('/', function (req, res) {
    client.on('connection', function (socket) {
        console.log("Connection")
    });
    res.render('chat');
});

app.listen(config.server.port, function() {
    console.log("Listening on port " + config.server.port);
});

Client:

try {
        var socket = io.connect('http://127.0.0.1:8080');

    } catch(e) {
        //Set status to warn user
        console.log(e);
    }

The problem is that if you add socket.io event listeners inside of an express route handler multiple listeners are created on the socket. If you were to create the pug file and test this code you would notice the console logging "connection" once on first refresh twice on second and so on because each time the route is handled another event listener is being added. I could fix it by moving the listener outside of the route handler however I need to be able to access "req". Is there any solution to this that would allow me to access "req" and prevent excess listeners from being added?

Upvotes: 2

Views: 4348

Answers (3)

Werlious
Werlious

Reputation: 594

A bit late to the party, but it seems like OP wants to access the req object to verify login. To add my 2 pennies to the mix, this is what I've done in a similar situation:

With my app using cookie based login tokens (over https only!), I was able to access the login cookie using

function cookieParser(cookief) {
    let cookies = {}
    let _cookies = cookief.split("; ")
    for(cookiekv of cookief.split("; ")) {
        let kv = cookiekv.split("=")
        cookies[kv[0]] = kv[1]
    }
    return cookies
}
function verifyLogin(cookies) {
    //verify login cookies here. Change this example
    return cookies.loginToken
}
io.use(function(socket,next) {
    const cookies = cookieParser(socket.handshake.headers.cookie)
    if(verifyLogin(cookies)) {
        next()
    } else {
        next(new Error("invalid login token"))
    } 
})

This allowed me to only accept socket connections from users that have logged in and received a login token. This should also fix your multiple listeners scenario since only one listener is registered on io.

Hope this helps out!

Upvotes: 0

Kirk
Kirk

Reputation: 16

You can see the recorded event listeners by console logging the io.sockets

Namespace {
  _events:
   { connection: [ [Function], [Function], [Function], [Function] ] },
  _eventsCount: 1 }

To fix your problem, simply put this condition before you initialize the socket connection

  if(io.sockets._events == undefined) {
    io.on('connection', socket => {
      ...
    });
  }

In case there are other event listeners aside from connection

  if(!('connection' in io.sockets._events)) {
    io.on('connection', socket => {
      ...
    });
  }

Upvotes: 0

shaochuancs
shaochuancs

Reputation: 16226

Unfortunately, the req object accessed in the socket.io connection event listener is the req when the event listener is declared, NOT the req when the event listener is executed. Thus, the expected behavior mentioned in the question (if my understanding is correct) is impossible.

Here is a simple experiment, for the code in question:

app.get('/', function (req, res) {
  client.on('connection', function (socket) {
    console.log("req.url: " + req.url)
  });
  res.render('chat');
});

If browser is used to send 2 HTTP request: first GET /?q=42 and then GET /?q=88, the console.log result would be:

//first request
req.url: /?q=42

//second request
req.url: /?q=42
req.url: /?q=88

For the second request, as connection event is listened twice, the event listener would be executed twice too. However, the execution result is different -- the event listener attached in the first HTTP request remember the req object value at that time.


If there is only one client, and no concurrent requests (a very restricted situation), there is a workaround -- save req as currentReq, and make event listener deal with currentReq:

var currentReq;
var isListened = false;

// write logic in middleware, so it can be used in all routes.
app.use(function(req, res, next) {
  currentReq = req;
  if (!isListened) {
    client.on('connection', function (socket) {
      console.log("req.url: " + currentReq.url)
    });
    isListened = true;
  }
  next();
});

app.get('/', function (req, res) {
  res.render('chat');
});

Here is some thinking about why this is impossible.

The scenario in the question is:

  1. Browser send HTTP GET request to Node.js
  2. Node.js return HTML page to browser
  3. Browser parse and render HTML page
  4. Browser send WebSocket connection request to Node.js
  5. Node.js establish WebSocket connection, and print log on console.

It is clear that when connection event happens (step 5), the HTTP req (step 1) has long gone. There is no way to restore initial HTTP request information in step 5, as WebSocket and HTTP are different connection, on different port.

Upvotes: 2

Related Questions