Методы отладки приложений на socket.io
Socket.io — популярная библиотека для организации передачи данных от браузера серверу и наоборот. Использовать ее достаточно просто, но на больших проектах возникает проблема отладки как сервера, так и клиента.
Предположим мы делаем чат, в котором пользователи общаются между собой. Приложение состоит из браузерного клиента и сервера на node.js. Если приложение делает один человек, то проблем с отладкой скорее всего не возникнет: он откроет Chrome Web Tools и посмотрит, что и куда запрашивается. Но когда клиент и сервер делают разные люди, может получиться, что сервер уже готов, а клиент еще нет или наоборот.
Для примера возьмем такую реализацию.
Со стороны сервера у нас имеется 3 cобытия:
- register
- login
- message
Алгоритм действий:
- register, передаем
login
иpassword
. Данные передаются первым аргументом в объекте:{login: ‘pavel’, password: ‘1234’}
. Вторым аргументом идет callback, который получит ошибку, если она есть. Callback соответствует принятому в node.js - первый параметр - ошибка, если есть, второй - ответ. - login, данные те же, что и у register. В callback передается
accessToken
, если ошибок нет. - message -
{token: '{accessToken}', message: '{text}'}
Отвечать на пришедшее сообщение необязательно отправкой другого сообщения, можно передать последним аргументом callback и просто вызывать его как обычный callback в любой другой ситуации. Подробнее об этом.
Входящее событие message содержит данные вида {message: ‘<text>’, user: ‘<username>’}
Рассмотрим, как отладить сервер, если у нас ещё не готов клиент.
Встроенные логи socket.io
Сама библиотека предоставляет некоторые возможности для отладки. Включить их можно с помощью localStorage.debug='*'
в браузере или запуская сервер с переменной окружения DEBUG=*
. Используется библиотека debug, предоставляющая удобные логи, которые можно гибко настраивать, указывая параметры вместо *
в выражениях выше.
В нашем случае такой способ сам по себе мы использовать не можем, поскольку нужен клиент, но его можно применять в совокупности с другими методами.
Во всех последующих примерах предполагается что сервер уже запущен с логированием, его адрес http://localhost:8888/.
Chrome Dev Tools
В первом методе предполагается написание нужного для проверки кода прямо в консоли браузера, со всеми подключениями к сокету, навешиванием событий и т.п.
Попробуем отправить сообщение, используя такой метод. Для начала нужно найти страницу, где уже подключена библиотека socket.io, либо написать свою. Как вариант, зайти на сайт http://socket.io/.
Открываем консоль, создаем соединение:
var socket = io('http://localhost:8888');
В консоли сервера видим, что подключение состоялось:
Теперь можем регистрироваться, логиниться и пробовать отправить что-нибудь:
socket.emit('register', {login: 'pavel', password: '1234'}, err => console.error(err));
socket.emit('login', {login: 'pavel', password: '1234'}, (err, token) => err ? console.error(err): window.token = token);
Сразу становится очевиден минус такого подхода: приходится писать все в одну строку, что катастрофически сказывается на читабельности.
Допустим ошибок не возникло, тогда в переменной window.token
будет токен юзера, который нужен для отправки сообщений.
В консоли сервера видим, что сообщения пришли:
И на login
пришел ответ:
Теперь подпишемся на событие message
, чтобы получить сообщение:
socket.on('message', data => console.log(data));
Логика отправки сообщений проста — при получении события, сервер проверяет правильность токена, и если все хорошо, пересылает сообщение всем подключенным клиентам, включая того, кто его отправил. Может быть это не самое удачное решение, но для демонстрации сгодится.
Отправляем само сообщение:
socket.emit('message', {message: 'test', token: window.token});
Сообщение пришло, сервер подставил имя автора дополнительно к тексту:
Таким нехитрым образом можно проверять простую логику или наличие соединения. Но что-то более сложное сделать проблематично — приходится все записывать в одну строку, либо использовать Shift+Enter
в консоли, чтобы сделать переход на новую, но об этом легко забыть и выполнить еще недописанный код.
Отдельная HTML-страничка
Логичным продолжением написания кода в консоли будет вынесение его в отдельный файл. Это избавляет от необходимости каждый раз переписывать все команды заново, достаточно перезагрузить страницу. Результат работы можно выводить в ту же консоль, отображать в HTML, в крайнем случае показывать alert-ами.
Делать мы будем то же самое, что и в прошлом примере. В итоге получим макет клиента. Для более сложных приложений можно разбить отдельные части по разным файлам, например отдельно авторизация, отдельно чат, отдельно звонки и т.п.
Внешне клиент выглядит примерно следующим образом:
Здесь есть необходимые формы, а также поля с отладочной информацией. Ознакомиться с кодом можно тут.
По сравнению с предыдущим вариантом добавилась обработка ошибок и интерфейс. При этом мы можем все так же открыть консоль и выполнить некоторый код там, если нам нужна интерактивность. Но отдельный файл можно поместить в репозиторий и им например могут воспользоваться тестировщики. Часто таким образом делают всевозможные генераторы. Допустим нам нужно иметь в системе 100 пользователей. Их, конечно же, можно зарегистрировать вручную, но это займет уйму времени. А если их надо не 100, а 1000? Или 10000? Тут на помощь приходит генератор. Для нашего случая создаем форму с параметрами: количество пользователей и пароль. И регистрируем пользователей:
for (var i=0; i < numUsers; i++) {
socket.emit('register', {login: 'user' + i, password: password});
}
Автоматизированные тесты
Улучшением предыдущего метода является написание тестов. В отличии от него, тесты могут (и должны) быть выполнены автоматически. Для node.js существует socket.io клиент, установим его:
npm install --save socket.io-client
Установим нужные зависимости для тестов:
npm install --save-dev mocha
npm install --save-dev should
Создаем файл теста, подключаем необходимые модули:
var should = require('should');
var io = require('socket.io-client');
var randToken = require('rand-token');
Задаем параметры:
var url = 'http://0.0.0.0:8888/';
var options = {
transports: ['websocket'],
'force new connection': true
};
Пишем пару тестов для примера. В реальном приложении их может быть значительно больше.
describe('Server', function () {
it('should register new user and login', function (done) {
var client = io.connect(url, options);
client.on('connect', function () {
var user = generateUser();
client.emit('register', user, function (err) {
should.not.exist(err);
client.emit('login', user, function (err, token) {
should.not.exist(err);
should(token).be.ok();
done();
});
});
});
client.on('connect_error', function (e) {
should.not.exist(e);
});
});
it('should send and receive message', function (done) {
var client = io.connect(url, options);
var message = randToken.generate(128);
client.on('connect', function () {
var user = generateUser();
client.emit('register', user, function (err) {
should.not.exist(err);
client.emit('login', user, function (err, token) {
should.not.exist(err);
should(token).be.ok();
client.on('message', function (data) {
should(data).have.properties('message', 'user');
if (data.user === user.login) {
should(data.message).be.equal(message);
done();
}
});
client.emit('message', {token: token, message: message}, function (err) {
should.not.exist(err);
});
});
});
});
client.on('connect_error', function (e) {
should.not.exist(e);
});
});
});
Полностью файл теста располагается тут.
Для удобства, допишем в package.json
команду тестирования:
"scripts": {
"test": "mocha index.test.js"
}
Запускаем: npm test
Если сервер запущен, то в консоли мы увидим:
Тесты прошли успешно. Далее можно добавлять еще тестов, проверить на возникающие ошибки, например.
Недостаток такого метода — нужно писать код тестов, расставлять проверки, но это компенсируется удобством дальнейшего использования. Также проблему представляет база данных. В более-менее сложном приложении без нее не обойтись, и для тестирования она нужна отдельная.
Специальное приложение или веб-сайт
Часто возникает ситуация, когда нужно не комплексное тестирование, а отладка в реальном времени, интерактивно. В таком случае методы 2 и 3 нам категорически не подходят, поскольку в них нужно учесть возможные ситуации заранее. Первый способ лучше в этом плане, но далек от идеала из-за необходимости писать много лишнего кода. Специальное приложение позволяет решить эту проблему — нам вообще не нужно писать код, мы отправили событие, посмотрели ответ, решили что делать, отправили второе событие и т.д. Тут клиентом выступаем мы сами, но нам нужен удобный транспорт для передачи событий.
Таких приложений не так уж и много и они не блещут функциональностью:
- http://scaret.in/socket.io-client/
- Возможно подключение к нескольким серверам одновременно
- Чтобы получать сообщения, на событие нужно подписаться
- Отправка только простых текстовых данных
- https://github.com/socketio/socket.io/issues/2014
- Возможна отправка объектов, формат YAML
- Не запоминает последние введенные данные
Как средство тестирования приложение — не лучший выбор, нужно постоянно помнить алгоритм, и не ошибаться при вводе. Оно больше подходит разработчику клиента, чтобы изучить API, посмотреть что откуда возвращается, какие приходят события и т.п.
Выводы
Каждый метод хорош по-своему, никто не запрещает использовать все, либо какие-то комбинации из них. Если нужно регулярное тестирование — на помощь придут автоматизированные тесты. Необходима интерактивность — используем приложение. Нужно сгенерировать что-то — больше всего подойдет специально созданная HTML-страница.