Redis, Node.js и Socket.io: межсерверная аутентификация и понимание node.js

Я получил одностраничное приложение, работающее на Rails и Backbone.js. Я использую Node.js через Redis для отправки и синхронизации данных с клиентами. Я пытаюсь понять, защитить и оптимизировать сокеты связи.

При записи с console.log я вижу несколько строк журнала (console.log('Redis connection on ..')) получают дубликаты (см. ниже).

Кто-нибудь может объяснить мне, почему? Существуют ли какие-то специфические поведения Node.js, которые я не понимаю из-за реализации моего кода?

Вот мой код Node.js:

var io = require('socket.io').listen(3003);
var redis = require('redis');
var cookie = require("cookie");
var redis_subs = redis.createClient(6379, "localhost");
var redis_auth = redis.createClient(6379, "localhost");
var authenticated = false;

var authentication_status = function (status) {
  if (status === true) {
    console.log('Authentication status is true.');
  }
  else {
    console.log('Authentication status is false.');
  }
  authenticated = status;
};

redis_subs.subscribe('application_channel');

io.configure(function () {
  io.set('authorization', function (data, callback) {
    console.log('Cross authentication...');
    if (data.headers.cookie) {
      data.cookie = cookie.parse(data.headers.cookie);
      data.sessionID = data.cookie['_validation_token_key'];
      redis_auth.hget(["sessionStore", data.sessionID], function (err, session) {
        if (err || !session) {
          console.log('Socket.io authorization say false');
          return callback(authentication_status(false), false);
        }
        else {
          data.session = JSON.parse(session);
          console.log('Socket.io authorization say true');
          return callback(authentication_status(true), true);
        }
      });
    }
    else {
      console.log('Socket.io authorization say false');
      return callback(authentication_status(false), false);
    }
  });
});

io.on('connection', function(socket){
    if (socket.handshake.session) {
      var user_socket_channel = socket.handshake.session['user_socket_channel']
      redis_subs.on('message', function(redis_channel, rawdata) {
        console.log('Redis connection on '+redis_channel);
        console.log('Handshaked Session, authenticated user. All channels allowed.');
        var data = JSON.parse(rawdata);
        if (data.channel) { var socket_channel = data.channel; }
        else { var socket_channel = user_socket_channel; }
        var rails_data = data.data;
        console.log('Socket.io emiting on ['+socket_channel+']')
        socket.emit(socket_channel, rails_data);
      });
    }
    else {
      redis_subs.on('message', function(redis_channel, rawdata) {
        console.log('Redis connection on '+redis_channel);
        console.log('No handshaked Session, unauthenticated user. Public channels allowed.');
        var data = JSON.parse(rawdata);
        var rails_data = data.data;
        console.log('Socket.io emiting on [public]')
        socket.emit('public', rails_data);
      });
    }
});

Вот способ, которым я тестирую это, и конкретный случай использования, когда журнал становится дубликатом:

Загрузка страницы во время входа в систему с помощью веб-браузера1 (например, Firefox); выход:

   info  - socket.io started
Cross authentication...
Socket.io authorization say true
Authentication status is true.
   debug - authorized
   info  - handshake authorized bJxm_IcWl2mKZT4Ed-kV
   debug - setting request GET /socket.io/1/websocket/bJxm_IcWl2mKZT4Ed-kV
   debug - set heartbeat interval for client bJxm_IcWl2mKZT4Ed-kV
   debug - client authorized for 
   debug - websocket writing 1::

Загрузка той же самой страницы, которая выходит из системы, с browser2 (например, Chrome)

Cross authentication...
Socket.io authorization say false
Authentication status is false.
   debug - authorized
   info  - handshake unauthorized

Отправка некоторых данных через страницу / браузер1 (Firefox) -> Rails -> Redis, вывод:

Redis connection on application_channel
Handshaked Session, authenticated user. All channels allowed.
Socket.io emiting on [hLTYXuvP+13aQlIT9CZiYc1i9eg=]
   debug - websocket writing 5:::{"name":"hLTYXuvP+13aQlIT9CZiYc1i9eg=","args":[null]}

Перезагрузка страницы в браузере 2 (Chrome, выход из системы), по-прежнему выводится

Cross authentication...
Socket.io authorization say false
Authentication status is false.
   debug - authorized
   info  - handshake unauthorized

Перезагрузка страницы в browser1 / Firefox и добавление дополнительных данных в Redis через страницу и Rails, вывод:

Redis connection on application_channel
Handshaked Session, authenticated user. All channels allowed.
Socket.io emiting on [hLTYXuvP+13aQlIT9CZiYc1i9eg=]
Redis connection on application_channel
Handshaked Session, authenticated user. All channels allowed.
Socket.io emiting on [hLTYXuvP+13aQlIT9CZiYc1i9eg=]
   debug - websocket writing 5:::{"name":"hLTYXuvP+13aQlIT9CZiYc1i9eg=","args":[null]}

Как видите, событие дублировалось, и слушатель Redis теперь перехватывает два соединения; Каждый раз, когда я перезагружаю page1 / Firefox, он дублирует его еще раз (3,4,5 ...).

Я не понимаю это поведение. Что мне не хватает? Кстати, как вы можете видеть, я не совсем понимаю цель Socket.ioconfigure, set authorization а такжеcallback, возвращая истину или ложь, когда узел достигаетio.on('connection') и Redis слушатели в любом случае.

 Zlatko14 февр. 2014 г., 02:28
Я вижу, у вас есть две подписки на клиенте redis_subs. Один для неаутентифицированного пользователя, другой для аутентифицированного. И в вашем журнале Firefox я вижу, что вы сначала вошли как неаутентифицированный пользователь, затем вы, вероятно, делаете эту подписку, затем вы аутентифицируете и делаете другую подписку. Итак, теперь у вас есть два слушателя. Но это немного гадание, писать без терминала, чтобы проверить это.

Ответы на вопрос(1)

Решение Вопроса

у меня есть мой ответ. Мне было трудно понять это, искал часами ...

Вот связанная тема, которая позволила мне заставить это работать (ответ И комментарии):

Как удалить Redis на слушателях 'message'

И вот мой код:

io.on('connection', function(client){
    console.log('Client id #'+client.id);

    if (client.handshake.session) {
      var user_socket_channel = client.handshake.session['user_socket_channel'];
    }

    var redis_listener = function(redis_channel, rawdata) {
      console.log('Redis connection on '+redis_channel);
      var data = JSON.parse(rawdata);
      if (data.channel) { var socket_channel = data.channel; }
      else { 
        if (user_socket_channel) { var socket_channel = user_socket_channel; }
        else { var socket_channel = 'public' }
      }
      var rails_data = data.data;
      console.log('Socket.io emiting on ['+socket_channel+']');
      client.emit(socket_channel, rails_data);
    };

    redis_subs.on('message', redis_listener);

    client.on('disconnect', function(){
      console.log('Client disconnect, removing redis listener..');
      redis_subs.removeListener('message', redis_listener);
    });
});

В зависимости от контекста вам нужно вызвать redis.removeListener для события io.client.disconnect.

Вот вероятная альтернатива:

https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO

Кстати, этот связанный вопрос кажется похожим с точным ответом, но на самом деле это не так (не сработало). Более того, посмотрите на выделенный комментарий:

Как повторно использовать подключение Redis в socket.io?

Ваш ответ на вопрос