Reputation: 141
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
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
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
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:
GET
request to Node.jsIt 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