socket.io身份验证与共享会话数据,io.use()的工作原理

socket.io authentication with sharing session data, how io.use() works

本文关键字:io use 工作 身份验证 共享 会话 数据 socket      更新时间:2023-09-26

灵感来自如何与Socket.IO 1.x和Express 4.x共享会话?我以某种"干净"的方式实现了套接字身份验证,不需要使用cookie解析器,也不需要从头中读取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>

所以客户端的io('http://localhost:8080/abc');会向服务器发送初始的HTTP握手请求,服务器可以从中收集cookie和许多其他请求信息。因此,服务器可以通过套接字访问该初始请求。request.

我的第一个问题是,为什么握手请求不在express会话中间件的范围内?(更普遍地说,在app.use中间件的范围内?(在某种程度上,我期待着这个app.use(sessionMiddleware(在初始请求之前激发,然后轻松访问socket.request.session

其次,用io.use()定义的中间件将在哪些场景中启动?仅用于初始HTTP握手请求?似乎io.use()用于套接字相关的内容(问题是:什么东西(,而app.use用于标准请求。

我不太清楚为什么在上面的例子中io.use()io.of('/abc').use()之前被激发。我特意写了那个命令,把io.of('/abc').use()放在第一位,看看它能起作用吗。应该反过来写。

最后,链接问题中的一些人也指出了socket.request.res有时undefined导致应用程序崩溃,可以通过提供空对象而不是套接字.request.res来解决问题,比如:sessionMiddleware(socket.request, {}, next);,在我看来这是一个肮脏的黑客。socket.request.res屈服于undefined的原因是什么?

尽管@oLeduc有点正确,但还有一些事情需要解释。。

为什么握手的请求不在express会话中间件的范围内

这里最大的原因是express中的中间件是为处理特定于请求的任务而设计的。不是所有的,但大多数处理程序都使用标准的req, res, next语法。如果我可以说的话,套接字是"无请求"的。事实上,您拥有socket.request是由于握手的方式,并且它使用HTTP进行握手。所以socket.io的人把第一个请求入侵到了你的socket类中,这样你就可以使用它了。它不是由express团队设计的,用来处理套接字和TCP。

使用io.use((定义的中间件将在哪些场景中启动

CCD_ 15是CCD_。在express中,中间件是在每个请求上执行的,对吧?但是套接字没有请求,在每个套接字发射上使用中间件会很困难,所以它们让它在每个连接上执行。但是,除了在处理(和响应(实际请求之前堆叠和使用express中间件外,Socket.IO在连接时甚至在实际握手之前使用中间件!如果你愿意,你可以使用这种中间件来拦截握手,这非常方便(为了保护你的服务器免受垃圾邮件的攻击(。更多信息可以在护照插槽的代码中找到

为什么io.use((在io.of('/abc'(.use((之前触发

关于这一点的真正解释可以在这里找到,这就是代码:

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侦听器,但因为NamespaceEventEmitter,所以侦听器会一个接一个地添加,所以它们会一个又一个地启动。换句话说,默认名称空间的侦听器位于第一位,因此它首先启动。

我不认为这是故意设计的,但它恰好是这样的:(

为什么socket.request.res未定义

老实说,我对此不太确定。这是因为engine.io是如何实现的——你可以在这里阅读更多。它连接到常规服务器,并发送请求以进行握手。我只能想象,有时在出现错误时,标头会与响应分离,这就是为什么你不会得到任何响应的原因。无论如何,仍然只是猜测。

希望信息能有所帮助。

为什么握手的请求不在express会话中间件的范围内

因为socket.io将附加到http。服务器,它是express下的层。在socket.io.的来源中的一条评论中提到了这一点

原因是第一个请求是一个常规的http请求,用于将请求的无状态http连接升级为状态完整的websocket连接。因此,它必须经历适用于常规http请求的所有逻辑,这没有多大意义。

使用io.use((定义的中间件将在哪些场景中启动

每当创建新的套接字连接时。

因此,每当客户端连接时,它都会调用使用io.use((注册的中间件。然而,一旦客户端连接,当从客户端接收到数据包时,它就不会被调用。无论连接是在自定义名称空间还是在主名称空间上启动,它都将被调用。

为什么io.use((在io.of('/abc'(.use((之前触发

名称空间是socket.io实现的一个细节,实际上,websocket总是首先到达主名称空间。

为了说明这种情况,请查看这个片段及其产生的输出:

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团队在文档中添加的警告:

重要提示:命名空间是Socket.IO协议的实现细节,与底层传输的实际URL无关,默认为/Socket.IO/…

为什么socket.request.res未定义

据我所知,它永远不应该被定义。它可能与您的具体实现有关。