【问题标题】:socket.io authentication with sharing session data, how io.use() workssocket.io 身份验证与共享会话数据,io.use() 如何工作
【发布时间】:2015-12-06 02:02:20
【问题描述】:

How to share sessions with Socket.IO 1.x and 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 和许多其他请求信息。因此服务器可以通过 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导致应用程序崩溃,可以通过提供解决问题空对象而不是 socket.request.res,例如:sessionMiddleware(socket.request, {}, next); 在我看来这像是一个肮脏的黑客。 socket.request.res 屈服于undefined 的原因是什么?

【问题讨论】:

    标签: javascript node.js express socket.io express-session


    【解决方案1】:

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

    为什么握手的请求不在 express-session 中间件的范围内?

    这里最大的原因是 express 中的中间件旨在处理请求特定的任务。不是全部,但大多数处理程序都使用标准的req, res, next 语法。如果我可以说,套接字是“无请求的”。您拥有socket.request 的事实是由于握手的方式,并且它为此使用了HTTP。因此,socket.io 的人将第一个请求入侵到您的套接字类中,以便您可以使用它。它不是由 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 侦听器,但由于NamespaceEventEmitter,侦听器是一个接一个添加的,因此它们会一个接一个地触发。换句话说,默认命名空间首先有它的监听器,所以它首先触发。

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

    为什么 socket.request.res 未定义?

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

    希望信息有所帮助。

    【讨论】:

      【解决方案2】:

      为什么握手的请求不在 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 总是会首先访问主命名空间。

      为了说明情况,看看这个 sn-p 和它产生的输出:

      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 未定义?

      据我所知,它不应该是未定义的。这可能与您的具体实现有关。

      【讨论】:

        猜你喜欢
        • 2014-01-24
        • 1970-01-01
        • 2016-01-22
        • 2013-08-08
        • 2016-06-04
        • 2016-04-24
        • 1970-01-01
        • 2011-08-09
        • 2021-05-20
        相关资源
        最近更新 更多