Аутентификация socket.io с обменом данными сеанса, как работает io.use()

Вдохновленный Как делиться сеансами с Socket.IO 1.x и Express 4.x? я реализовал аутентификацию сокета каким-то "чистым" способом, где нет необходимости использовать куки-парсер и читать куки из заголовков, но некоторые моменты остаются для меня неясными . В примере используется последняя стабильная версия 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.

Мой первый вопрос: почему запрос рукопожатия не входит в область промежуточного программного обеспечения экспресс-сеанса? (В более общем смысле в область промежуточного программного обеспечения 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 приводит к поломке приложения, проблема может быть решена путем предоставления пустого объекта вместо < strong>socket.request.res, например: sessionMiddleware(socket.request, {}, next);, что мне кажется грязным взломом. По каким причинам socket.request.res уступает undefined?


person Srle    schedule 09.09.2015    source источник


Ответы (2)


Несмотря на то, что @oLeduc в некотором роде прав, есть еще несколько вещей, которые нужно объяснить.

Почему запрос рукопожатия не относится к промежуточному программному обеспечению экспресс-сеанса?

Основная причина здесь в том, что промежуточное ПО в экспрессе предназначено для обработки конкретных задач запроса. Не все, но большинство обработчиков используют стандартный синтаксис req, res, next. И сокеты "без запроса", если я могу сказать. Тот факт, что у вас есть socket.request, связан с тем, как выполняется рукопожатие, и что для этого он использует HTTP. Итак, ребята из socket.io взломали этот первый запрос в вашем классе сокетов, чтобы вы могли его использовать. Он не был разработан командой экспресс-доставки для работы с сокетами и TCP.

Каковы сценарии, в которых сработает ПО промежуточного слоя, определенное с помощью io.use()?

io.use является близким представлением способа промежуточного программного обеспечения Express use. В экспрессе промежуточное ПО выполняется при каждом запросе, верно? Но у сокетов нет запросов, и будет неудобно использовать промежуточное ПО для каждого эмитируемого сокета, поэтому они сделали так, чтобы оно выполнялось при каждом соединении. Но так же, как экспресс-промежуточное ПО складывается и используется до обработки фактического запроса (и ответа), Socket.IO использует промежуточное ПО при подключении и даже до фактического рукопожатия! Вы можете перехватить рукопожатие, если хотите, используя такое промежуточное программное обеспечение, которое очень удобно (для защиты вашего сервера от спама). Подробнее об этом можно узнать в коде passport-socketio.

Почему 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, но поскольку Namespace — это EventEmitter, слушатели добавляются один за другим, поэтому они срабатывают один за другим. Другими словами, в пространстве имен по умолчанию слушатель находится на первом месте, поэтому он срабатывает первым.

Не думаю, что это сделано специально, просто так получилось :)

Почему socket.request.res не определен?

Честно говоря, я не очень в этом уверен. Это связано с тем, как реализован engine.io — вы можете прочитать немного больше здесь. Он подключается к обычному серверу и отправляет запросы на рукопожатие. Я могу только представить, что иногда при ошибках заголовки отделяются от ответа, и поэтому вы их не получите. В любом случае, пока только гадать.

Надеюсь информация поможет.

person Andrey Popov    schedule 16.09.2015

Почему запрос рукопожатия не относится к промежуточному программному обеспечению экспресс-сеанса?

Потому что socket.io будет подключаться к http.Server, который является уровнем под экспрессом. Это упоминается в комментарии в источнике. из socket.io.

Причина этого в том, что первый запрос является обычным HTTP-запросом, используемым для обновления обычного HTTP-соединения без сохранения состояния до соединения веб-сокета с полным состоянием. Таким образом, не имеет особого смысла выполнять всю логику, применимую к обычным http-запросам.

Каковы сценарии, в которых сработает ПО промежуточного слоя, определенное с помощью io.use()?

Всякий раз, когда создается новое соединение сокета.

Таким образом, каждый раз, когда клиент подключается, он будет вызывать ПО промежуточного слоя, зарегистрированное с помощью io.use(). Однако после подключения клиента он не вызывается при получении пакета от клиента. Неважно, инициировано ли соединение в пользовательском пространстве имен или в основном пространстве имен, оно всегда будет вызываться.

Почему io.use() срабатывает раньше io.of('/abc').use()?

Пространства имен — это деталь реализации socket.io, на самом деле веб-сокеты всегда сначала будут обращаться к основному пространству имен.

Чтобы проиллюстрировать ситуацию, посмотрите на этот фрагмент и его вывод:

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 не определен?

Насколько я знаю, он никогда не должен быть неопределенным. Это может быть связано с вашей конкретной реализацией.

person oLeduc    schedule 16.09.2015