Примечание: Статья написана для Express v 2.x, весь код, что здесь описан, не будет работать в новых версиях Express v 3.x без модификаций.
Express — великолепный фреймворк для разработки веб-приложений на Node.js. Он позволяет легко работать с маршрутиризацией адресов страниц, запросами и сессиями. socket.io — абстрактный слой над Websockets, с Flash и XHR оберткой, которая запускается как на сервере, так и на клиенте.
Основы
Socket.io легко можно запустить работать вместе с Express. Просто вызываем socket.io listen метод и передаем его в Express как параметр.
var io = require('socket.io'), express = require('express'), app = express.createServer(); app.configure(function () { app.use(express.cookieParser()); app.use(express.session({secret: 'secret', key: 'express.sid'})); app.use(function (req, res) { res.end('<h2>Hello, your session id is ' + req.sessionID + '</h2>'); }); }); app.listen(); var sio = io.listen(app); sio.sockets.on('connection', function (socket) { console.log('A socket connected!'); }); |
Тем не менее, socket.io не будет связан с Express, и наоборот. Так что, если есть соединение socket.io с клиентом, то мы не сможем узнать, к какой сессии Express принадлежит это соединение, в большинстве случаев это важная информация. Начиная с версии 0.7 socket.io мы можем получить эту информацию через механизм авторизации. Благодаря этому, мы можем заставить socket.io вызвать пользовательскую функцию, когда создается новое соединение Websockets. Важный момент, это то, что функция должна вызываться до завершения соединения с сокетом. Это дает множество преимуществ. С одной стороны, мы можем принять или отменить связи, основанные в различных условиях и, с другой стороны это позволяет нам проверить информацию заголовка запроса HTTP, который пытается установить WebSocket — в том числе cookie. Cookie содержат идентификатор текущей сессии.
var parseCookie = require('connect').utils.parseCookie; sio.set('authorization', function (data, accept) { // check if there's a cookie header if (data.headers.cookie) { // if there is, parse the cookie data.cookie = parseCookie(data.headers.cookie); // note that you will need to use the same key to grad the // session id, as you specified in the Express setup. data.sessionID = data.cookie['express.sid']; } else { // if there isn't, turn down the connection with a message // and leave the function. return accept('No cookie transmitted.', false); } // accept the incoming connection accept(null, true); }); |
Все атрибуты, назначенные в объект данных сейчас доступны благодаря авторизованным атрибутам объекта соединения socket.io.
sio.sockets.on('connection', function (socket) { console.log('A socket with sessionID ' + socket.handshake.sessionID + ' connected!'); }); |
Теперь все серьезно, получение
Что я нахожу гораздо более интересным, является то, что мы можем не только извлечь SessionID из cookies, мы также можем загружать фактическую сессию и использовать, изменять или даже разрушить ее. Для этого нам необходимо получить хранилище, которое Express использует для хранения сессий: по-умолчанию, это хранилище в памяти, оно создается самим Express. Вместо него, мы можем создать свое собственное хранилище и передать его в Express. Таким образом, у нас будет ссылка на свое хранилище.
var io = require('socket.io'), express = require('express'), MemoryStore = express.session.MemoryStore, app = express.createServer(), sessionStore = new MemoryStore(); app.configure(function () { app.use(express.cookieParser()); app.use(express.session({store: sessionStore , secret: 'secret' , key: 'express.sid'})); app.use(function (req, res) { res.end('<h2>Hello, your session id is ' + req.sessionID + '</h2>'); }); }); |
Сейчас, в нашей функции авторизации (в оригинале рукопожатия) мы можем получить не только идентификатор сессии из кук, но и загрузить текущие данные из хранилища сессии.
sio.set('authorization', function (data, accept) { if (data.headers.cookie) { data.cookie = parseCookie(data.headers.cookie); data.sessionID = data.cookie['express.sid']; // (literally) get the session data from the session store sessionStore.get(data.sessionID, function (err, session) { if (err || !session) { // if we cannot grab a session, turn down the connection accept('Error', false); } else { // save the session data and accept the connection data.session = session; accept(null, true); } }); } else { return accept('No cookie transmitted.', false); } }); |
Хорошие вещи
Благодаря socket.handshake.session, у нас сейчас есть доступ ко всем данным сессии. Но для того, чтобы изменить данные сессии, необходимо создать фактический Session объект Express. Конструктор Express Session объект требует запрос, связанный с сессией, а также с ее данными. Мы только что получили данные сессии из хранилища сеанса, так что они у нас есть. Но у нас нет доступа к HTTP запросу. socket.io не предоставляет это. Если присмотреться к прототипу Session, увидите только 3 свойства, необходимые объекту запроса и переданные в конструктор: sessionID, session и sessionStore. Это хорошая новость, так как наш объект авторизации данных уже имеет свойства sessionID и session. И оба со значениями (когда создается объект сеанса), которые ожидает прототип Session. Последнее свойство sesisonStore можем легко добавить в объект данных.
var Session = require('connect').middleware.session.Session; sio.set('authorization', function (data, accept) { if (data.headers.cookie) { data.cookie = parseCookie(data.headers.cookie); data.sessionID = data.cookie['express.sid']; // save the session store to the data object // (as required by the Session constructor) data.sessionStore = sessionStore; sessionStore.get(data.sessionID, function (err, session) { if (err || !session) { accept('Error', false); } else { // create a session object, passing data as request and our // just acquired session data data.session = new Session(data, session); accept(null, true); } }); } else { return accept('No cookie transmitted.', false); } }); |
Сейчас можем использовать, изменять, удалять объект сеанса. Можно использовать touch() метод для сохранения сеанса на длительное время, пока есть соединение Websockets.
sio.sockets.on('connection', function (socket) { var hs = socket.handshake; console.log('A socket with sessionID ' + hs.sessionID + ' connected!'); // setup an inteval that will keep our session fresh var intervalID = setInterval(function () { // reload the session (just in case something changed, // we don't want to override anything, but the age) // reloading will also ensure we keep an up2date copy // of the session with our connection. hs.session.reload( function () { // "touch" it (resetting maxAge and lastAccess) // and save it back again. hs.session.touch().save(); }); }, 60 * 1000); socket.on('disconnect', function () { console.log('A socket with sessionID ' + hs.sessionID + ' disconnected!'); // clear the socket interval to stop refreshing the session clearInterval(intervalID); }); }); |
Собираем все вместе
С этих основных листингов кода можно продолжить. У вас есть все, чтобы создать приложение-гибрид express — socket.io, использовать его компоненты как угодно. Если хотите посмотреть как Express и socket.io работают вместе, взгляните на пример.
Статья является вольным переводом статьи Daniel Baulig socket.io and Express. Tying it all together.