socket.io 共享 session 数据的身份验证,io.use() 的工作原理
socket.io authentication with sharing session data, how io.use() works
受 How to share sessions with Socket.IO 1.x and Express 4.x? 启发,我以某种 "clean" 方式实现了套接字身份验证,无需使用 cookie-parser 并从 headers 读取 cookie,但剩下的项目很少我不清楚。示例使用最后一个稳定 socket.io 版本 1.3.6.
var express = require('express'),
session = require('express-session'),
RedisStore = require('connect-redis')(session),
sessionStore = new RedisStore(),
io = require('socket.io').listen(server);
var sessionMiddleware = session({
store : sessionStore,
secret : "blabla",
cookie : { ... }
});
function socketAuthentication(socket, next) {
var sessionID = socket.request.sessionID;
sessionStore.get(sessionID, function(err, session) {
if(err) { return next(err); }
if(typeof session === "undefined") {
return next( new Error('Session cannot be found') );
}
console.log('Socket authenticated successfully');
next();
});
}
io.of('/abc').use(socketAuthentication).on('connection', function(socket) {
// setup events and stuff
});
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
app.use(sessionMiddleware);
app.get('/', function(req, res) { res.render('index'); });
server.listen(8080);
index.html
<body>
...
<script src="socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost:8080/abc');
</script>
</body>
所以 client-side 中的 io('http://localhost:8080/abc');
将向服务器发送初始 HTTP 握手请求,服务器可以从那里收集 cookie 和许多其他请求信息。因此服务器可以通过 socket.request 访问该初始请求。
我的第一个问题是为什么握手请求不在 express-session 中间件的范围内?(更普遍的是在 app.use
中间件的范围内?)在某种程度上我预料到这个 app.use(sessionMiddleware); 在初始请求之前触发,然后轻松访问 socket.request.session
其次,io.use()
定义的中间件在什么场景下会触发?仅针对初始 HTTP 握手请求?似乎 io.use()
用于与套接字相关的 stuff(问题是:什么 stuff),而 app.use
用于标准请求。
我不太清楚为什么在上面的示例中 io.use()
在 io.of('/abc').use()
之前被触发。我故意写下了将 io.of('/abc').use()
放在首位的命令,以查看它是否有效并且有效。
应该反过来写。
最后,socket.request.res 就像一些人在相关问题中指出的那样,有时 是 undefined
导致应用程序崩溃,可以通过提供空 object 而不是 socket.request.res 来解决问题,例如:sessionMiddleware(socket.request, {}, next);
在我看来一个肮脏的黑客。出于什么原因 socket.request.res 屈服于 undefined
?
为什么握手的请求不在 express-session 中间件的范围内?
因为 socket.io 将附加到 http.Server,这是 express 下的层。它在 socket.io 的 source 的评论中提到。
这是因为第一个请求是一个常规的 http 请求,用于将常规的无状态 http 连接升级为有状态的 websocket 连接。因此,它必须通过适用于常规 http 请求的所有逻辑没有多大意义。
用io.use()定义的中间件在什么情况下会触发?
每当创建新的套接字连接时。
因此每次客户端连接时,它都会调用使用 io.use() 注册的中间件。但是,一旦客户端连接,当从客户端接收到数据包时,它不会被调用。无论连接是在自定义命名空间上还是在主命名空间上发起的,都将始终被调用。
为什么 io.use() 在 io.of('/abc').use() 之前触发?
命名空间是 socket.io 实现的细节,实际上,websockets 总是首先访问主命名空间。
为了说明情况,请查看此代码段及其产生的输出:
var customeNamespace = io.of('/abc');
customeNamespace.use(function(socket, next){
console.log('Use -> /abc');
return next();
});
io.of('/abc').on('connection', function (socket) {
console.log('Connected to namespace!')
});
io.use(function(socket, next){
console.log('Use -> /');
return next();
});
io.on('connection', function (socket) {
console.log('Connected to namespace!')
});
输出:
Use -> /
Main namespace
Use -> /abc
Connected to namespace!
请参阅 socket.io 团队添加到其文档中的警告:
Important note: The namespace is an implementation detail of the Socket.IO protocol, and is not related to the actual URL of the underlying transport, which defaults to /socket.io/….
为什么 socket.request.res 未定义?
据我所知,它永远不应该是未定义的。可能跟你的具体实现有关。
尽管@oLeduc 是正确的,但还有一些事情需要解释..
为什么握手的请求不在express-session中间件的范围内?
这里最大的原因是 express 中的中间件旨在处理请求特定任务。不是全部,但大多数处理程序都使用标准的 req, res, next
语法。如果我能说的话,套接字是 "request-less"。你有 socket.request
的事实是由于握手的方式,并且它为此使用 HTTP。所以 socket.io 的人将第一个请求黑进了你的套接字 class 以便你可以使用它。它不是由 express 团队设计来使用套接字和 TCP。
用io.use()定义的中间件在什么情况下会触发?
io.use
是 express use
中间件方式的近似表示。在 express 中,中间件是在每个请求上执行的,对吧?但是套接字没有请求,在每个套接字发出时使用中间件会很尴尬,所以他们让它在每个连接上执行。但是,除了在处理(和响应)实际请求之前堆叠和使用 express 中间件之外,Socket.IO 在连接时甚至 在 实际握手之前使用中间件!如果你愿意,你可以拦截握手,使用那种非常方便的中间件(为了保护你的服务器免受垃圾邮件)。有关更多信息,请参阅 passport-socketio
的代码
为什么 io.use() 在 io.of('/abc').use() 之前触发?
真正的解释可以在here中找到,也就是这段代码:
Server.prototype.of = function(name, fn){
if (String(name)[0] !== '/') name = '/' + name;
if (!this.nsps[name]) {
debug('initializing namespace %s', name);
var nsp = new Namespace(this, name);
this.nsps[name] = nsp;
}
if (fn) this.nsps[name].on('connect', fn);
return this.nsps[name];
};
而在代码的开头,有这行代码:
this.sockets = this.of('/');
所以,一开始就创建了一个默认的命名空间。就在那里,您可以看到它立即附加了一个 connect
侦听器。后来,每个命名空间都获得了完全相同的 connect
侦听器,但是因为 Namespace
是 EventEmitter
,侦听器是一个接一个地添加的,所以它们会一个接一个地触发。换句话说,默认命名空间首先有它的侦听器,因此它首先触发。
我不认为这是故意设计的,但恰好是这样:)
为什么 socket.request.res 未定义?
老实说,我不太确定。这是因为 engine.io 是如何实现的——你可以多读一点 here。它连接到常规服务器,并发送请求以进行握手。我只能想象有时在出现错误时 headers 会与响应分开,这就是为什么您不会得到任何响应的原因。无论如何,仍然只是猜测。
希望信息有所帮助。
受 How to share sessions with Socket.IO 1.x and Express 4.x? 启发,我以某种 "clean" 方式实现了套接字身份验证,无需使用 cookie-parser 并从 headers 读取 cookie,但剩下的项目很少我不清楚。示例使用最后一个稳定 socket.io 版本 1.3.6.
var express = require('express'),
session = require('express-session'),
RedisStore = require('connect-redis')(session),
sessionStore = new RedisStore(),
io = require('socket.io').listen(server);
var sessionMiddleware = session({
store : sessionStore,
secret : "blabla",
cookie : { ... }
});
function socketAuthentication(socket, next) {
var sessionID = socket.request.sessionID;
sessionStore.get(sessionID, function(err, session) {
if(err) { return next(err); }
if(typeof session === "undefined") {
return next( new Error('Session cannot be found') );
}
console.log('Socket authenticated successfully');
next();
});
}
io.of('/abc').use(socketAuthentication).on('connection', function(socket) {
// setup events and stuff
});
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
app.use(sessionMiddleware);
app.get('/', function(req, res) { res.render('index'); });
server.listen(8080);
index.html
<body>
...
<script src="socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost:8080/abc');
</script>
</body>
所以 client-side 中的 io('http://localhost:8080/abc');
将向服务器发送初始 HTTP 握手请求,服务器可以从那里收集 cookie 和许多其他请求信息。因此服务器可以通过 socket.request 访问该初始请求。
我的第一个问题是为什么握手请求不在 express-session 中间件的范围内?(更普遍的是在 app.use
中间件的范围内?)在某种程度上我预料到这个 app.use(sessionMiddleware); 在初始请求之前触发,然后轻松访问 socket.request.session
其次,io.use()
定义的中间件在什么场景下会触发?仅针对初始 HTTP 握手请求?似乎 io.use()
用于与套接字相关的 stuff(问题是:什么 stuff),而 app.use
用于标准请求。
我不太清楚为什么在上面的示例中 io.use()
在 io.of('/abc').use()
之前被触发。我故意写下了将 io.of('/abc').use()
放在首位的命令,以查看它是否有效并且有效。
应该反过来写。
最后,socket.request.res 就像一些人在相关问题中指出的那样,有时 是 undefined
导致应用程序崩溃,可以通过提供空 object 而不是 socket.request.res 来解决问题,例如:sessionMiddleware(socket.request, {}, next);
在我看来一个肮脏的黑客。出于什么原因 socket.request.res 屈服于 undefined
?
为什么握手的请求不在 express-session 中间件的范围内?
因为 socket.io 将附加到 http.Server,这是 express 下的层。它在 socket.io 的 source 的评论中提到。
这是因为第一个请求是一个常规的 http 请求,用于将常规的无状态 http 连接升级为有状态的 websocket 连接。因此,它必须通过适用于常规 http 请求的所有逻辑没有多大意义。
用io.use()定义的中间件在什么情况下会触发?
每当创建新的套接字连接时。
因此每次客户端连接时,它都会调用使用 io.use() 注册的中间件。但是,一旦客户端连接,当从客户端接收到数据包时,它不会被调用。无论连接是在自定义命名空间上还是在主命名空间上发起的,都将始终被调用。
为什么 io.use() 在 io.of('/abc').use() 之前触发?
命名空间是 socket.io 实现的细节,实际上,websockets 总是首先访问主命名空间。
为了说明情况,请查看此代码段及其产生的输出:
var customeNamespace = io.of('/abc');
customeNamespace.use(function(socket, next){
console.log('Use -> /abc');
return next();
});
io.of('/abc').on('connection', function (socket) {
console.log('Connected to namespace!')
});
io.use(function(socket, next){
console.log('Use -> /');
return next();
});
io.on('connection', function (socket) {
console.log('Connected to namespace!')
});
输出:
Use -> /
Main namespace
Use -> /abc
Connected to namespace!
请参阅 socket.io 团队添加到其文档中的警告:
Important note: The namespace is an implementation detail of the Socket.IO protocol, and is not related to the actual URL of the underlying transport, which defaults to /socket.io/….
为什么 socket.request.res 未定义?
据我所知,它永远不应该是未定义的。可能跟你的具体实现有关。
尽管@oLeduc 是正确的,但还有一些事情需要解释..
为什么握手的请求不在express-session中间件的范围内?
这里最大的原因是 express 中的中间件旨在处理请求特定任务。不是全部,但大多数处理程序都使用标准的 req, res, next
语法。如果我能说的话,套接字是 "request-less"。你有 socket.request
的事实是由于握手的方式,并且它为此使用 HTTP。所以 socket.io 的人将第一个请求黑进了你的套接字 class 以便你可以使用它。它不是由 express 团队设计来使用套接字和 TCP。
用io.use()定义的中间件在什么情况下会触发?
io.use
是 express use
中间件方式的近似表示。在 express 中,中间件是在每个请求上执行的,对吧?但是套接字没有请求,在每个套接字发出时使用中间件会很尴尬,所以他们让它在每个连接上执行。但是,除了在处理(和响应)实际请求之前堆叠和使用 express 中间件之外,Socket.IO 在连接时甚至 在 实际握手之前使用中间件!如果你愿意,你可以拦截握手,使用那种非常方便的中间件(为了保护你的服务器免受垃圾邮件)。有关更多信息,请参阅 passport-socketio
为什么 io.use() 在 io.of('/abc').use() 之前触发?
真正的解释可以在here中找到,也就是这段代码:
Server.prototype.of = function(name, fn){
if (String(name)[0] !== '/') name = '/' + name;
if (!this.nsps[name]) {
debug('initializing namespace %s', name);
var nsp = new Namespace(this, name);
this.nsps[name] = nsp;
}
if (fn) this.nsps[name].on('connect', fn);
return this.nsps[name];
};
而在代码的开头,有这行代码:
this.sockets = this.of('/');
所以,一开始就创建了一个默认的命名空间。就在那里,您可以看到它立即附加了一个 connect
侦听器。后来,每个命名空间都获得了完全相同的 connect
侦听器,但是因为 Namespace
是 EventEmitter
,侦听器是一个接一个地添加的,所以它们会一个接一个地触发。换句话说,默认命名空间首先有它的侦听器,因此它首先触发。
我不认为这是故意设计的,但恰好是这样:)
为什么 socket.request.res 未定义?
老实说,我不太确定。这是因为 engine.io 是如何实现的——你可以多读一点 here。它连接到常规服务器,并发送请求以进行握手。我只能想象有时在出现错误时 headers 会与响应分开,这就是为什么您不会得到任何响应的原因。无论如何,仍然只是猜测。
希望信息有所帮助。