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::
Загрузка одной и той же страницы из браузера2 (например, Chrome)
Cross authentication...
Socket.io authorization say false
Authentication status is false.
debug - authorized
info - handshake unauthorized
Отправка некоторых данных через страницу /browser 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
Перезагрузка страницы в браузере1/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.io configure
, set authorization
и callback
, возвращающие true, либо false, поскольку Node достигает io.on('connection')
и Слушатели Redis в любом случае.
Ответы
Ответ 1
Хорошо, у меня есть мой ответ. Мне было трудно разобраться, искали часы...
Вот связанная тема, которая позволила мне заставить ее работать (ответ и комментарии):
Как удалить Redis на прослушивание сообщения "сообщение"
И вот мой код:
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?