Методы отладки приложений на socket.io

author

Socket.io — популярная библиотека для организации передачи данных от браузера серверу и наоборот. Использовать ее достаточно просто, но на больших проектах возникает проблема отладки как сервера, так и клиента.

Предположим мы делаем чат, в котором пользователи общаются между собой. Приложение состоит из браузерного клиента и сервера на node.js. Если приложение делает один человек, то проблем с отладкой скорее всего не возникнет: он откроет Chrome Web Tools и посмотрит, что и куда запрашивается. Но когда клиент и сервер делают разные люди, может получиться, что сервер уже готов, а клиент еще нет или наоборот.

Для примера возьмем такую реализацию.

Со стороны сервера у нас имеется 3 cобытия:

  • register
  • login
  • message

Алгоритм действий:

  1. register, передаем login и password. Данные передаются первым аргументом в объекте: {login: ‘pavel’, password: ‘1234’}. Вторым аргументом идет callback, который получит ошибку, если она есть. Callback соответствует принятому в node.js - первый параметр - ошибка, если есть, второй - ответ.
  2. login, данные те же, что и у register. В callback передается accessToken, если ошибок нет.
  3. 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-страница.

Плюсануть
Отправить
Поделиться