Идентификатор socket.io с данными сеанса совместного доступа, как работает io.use()
Вдохновленный Как обмениваться сеансами с 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 и многие другие запрашивать информацию. Таким образом, сервер имеет доступ к этому первоначальному запросу через socket.request.
Мой первый вопрос заключается в том, почему запрос на управление рукопожатием не входит в область промежуточного программного обеспечения экспресс-сессии? (В общем, в области app.use
middlewares?) В некотором роде я ожидал этого app.use(sessionMiddleware);, чтобы запустить этот первоначальный запрос, а затем легко получить доступ к socket.request.session
Во-вторых, каковы сценарии, в которых будут срабатывать middlewares, определенные с помощью io.use()
? Только для первоначального запроса на квитирование HTTP? Похоже, что io.use()
используется для сокетов 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
?
Ответы
Ответ 1
Несмотря на то, что @oLeduc является правильным, есть еще несколько вещей, чтобы объяснить.
Почему запрос квитирования не входит в область промежуточного программного обеспечения экспресс-сессии?
Самая большая причина здесь в том, что промежуточное ПО в экспресс-среде предназначено для обработки конкретных запросов. Не все, но большинство обработчиков используют стандартный синтаксис req, res, next
. И сокеты "без запроса", если можно сказать. Тот факт, что у вас есть socket.request
, связано с тем, как делается рукопожатие, и что для этого используется HTTP. Итак, ребята из socket.io взломали этот первый запрос в ваш класс сокетов, чтобы вы могли его использовать. Эксплуатационная команда не была разработана, чтобы работать с сокетами и TCP.
Каковы сценарии, в которых будут срабатывать middlewares, определенные с помощью io.use()?
io.use
является близким представлением пути экспресс-связи use
. В 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
, но поскольку Namespace
- EventEmitter
, слушатели добавляются один за другим, поэтому они запускаются один за другим. Другими словами, пространство имен по умолчанию имеет его слушатель на первом месте, поэтому он запускается первым.
Я не думаю, что это предназначено специально, но это случилось именно так:)
Почему socket.request.res undefined?
Честно говоря, я не совсем уверен в этом. Это из-за того, как реализовано engine.io - вы можете прочитать немного больше здесь. Он прикрепляется к обычному серверу и отправляет запросы для установления рукопожатия. Я могу только предположить, что иногда по ошибкам заголовки отделены от ответа и почему вы их не получите. В любом случае, все еще просто гадать.
Полезная информация помогает.
Ответ 2
Почему запрос квитирования не входит в область промежуточного программного обеспечения экспресс-сессии?
Потому что socket.io будет прикрепляться к http.Server, который является слоем под экспресс. Он упоминается в комментарии в источнике socket.io.
Причиной этого является то, что первый запрос является обычным HTTP-запросом, используемым для обновления HTTP-соединения с постоянным статусом без подключения к сети с полным доступом к сети. Поэтому для него не было бы смысла использовать всю логику, применимую к обычным HTTP-запросам.
Каковы сценарии, в которых будут срабатывать middlewares, определенные с помощью io.use()?
Всякий раз, когда создается новое соединение сокета.
Таким образом, каждый раз, когда клиент подключается, он вызывает вызовы middlewares с помощью 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 undefined?
Насколько я знаю, он никогда не должен быть undefined. Это может быть связано с вашей конкретной реализацией.