Issue
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?
Solution
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:
- Browser send HTTP
GET
request to Node.js - Node.js return HTML page to browser
- Browser parse and render HTML page
- Browser send WebSocket connection request to Node.js
- 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.