Использование WebSockets в Angular c RxJs WebSocketSubject

Создаем Angular Service для работы с вебсокетами

Alex Dukhnovskiy
Angular Soviet
6 min readJul 3, 2018

--

Кто я и для кого эта статья

Меня зовут Александр, я занимаюсь веб-разработкой уже более 10 лет, при этом последние 5 лет работаю вплотную с Angular/AngularJS. Являясь активным участником всевозможных сообществ, посвященных программированию, фронтенду и разработке, мне не раз доводилось отвечать на вопросы о вебсокетах, их применении и реализациях, как на клиенте, так и на стороне сервера.

В рамках Angular, а особенно в русскоязычном сегменте, вебсокеты представлены довольно скудно, а то, что имеется, зачастую хоть и решает проблему использования вебсокетов на проекте, но даёт весьма слабое понимание самой сути происходящего. Нередко же всё сводится примерно к такой подаче:

Здесь я постараюсь детально охватить узкую сферу применения технологии в рамках фреймворка Angular и его уже неотъемлемого помощника — RxJs, при этом мы намеренно не будем касаться серверных реализаций, т.к. это полноценная тема для отдельной статьи.

Эта статья будет полезна тем, кто уже знаком с Angular, но хочет углубить свои знания непосредственно по теме.

Для начала немного базовой информации.

Что такое WebSocket и почему он Вам нужен

Согласно Википедии, WebSocket это «протокол дуплексной связи (может передавать и принимать одновременно) поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени.

WebSocket разработан для воплощения в web-браузерах и web-серверах, но он может быть использован для любого клиентского или серверного приложения. Протокол WebSocket — это независимый протокол, основанный на протоколе TCP. Он делает возможным более тесное взаимодействие между браузером и web-сайтом, способствуя распространению интерактивного содержимого и созданию приложений реального времени.»

Говоря иначе, WebSocket позволяет серверу принимать запросы от клиента и слать запросы на клиент в любое нужное время, таким образом, браузер (клиент) и сервер при соединении получают равные права и возможность обмениваться сообщениями. Обычный AJAX-запрос требует передачи полных HTTP-заголовков, что означает увеличение трафика в обе стороны, тогда как накладные расходы вебсокетов после установления соединения составляют лишь два байта. Вебсокет уменьшает в сотни и тысячи раз количество передаваемой информации в HTTP-заголовках и в разы сокращает время ожидания. Соединения вебсокетов поддерживают кроссдоменность, подобно CORS.

На стороне сервера имеются свои пакеты для поддержки вебсокета, на клиенте же это HTML5 WebSocket API, которое имеет интерфейс из трех методов:

WebSocket — основной интерфейс для подключения к серверу WebSocket, а затем отправки и получения данных по соединению;

CloseEvent — событие, отправленное объектом WebSocket при закрытии соединения;

MessageEvent — событие, отправленное объектом WebSocket, когда сообщение получено с сервера.

Вот так это выглядит на уровне реализации JavaSript:

onmessage — слушаем сообщения от сервера

send — шлём свои сообщения на сервер

То есть в базовом виде всё предельно просто, но если у Вас есть желание углубиться в тему, можете обратиться к MDN Web Docs и заодно изучить библиотеки, которые реализуют свои собственные слои поверх данного API.

Почему не нужно бояться использовать WebSocket

Первый момент, который может отпугивать — это поддержка браузерами. На сегодняшний день такой проблемы нет — WebSocket поддерживается практически полностью как в вебе, так и в мобильном сегменте.

Поддержка веб-браузерами
Поддержка мобильными браузерами

Второй момент — простота реализации. Да, поначалу это обескураживает.

API настолько прост, что с первого взгляда может быть тяжело понять, как работать с таким скромным количеством методов, ведь все они, кроме одного, сообщают либо про ошибки, либо про соединение, и только один из них — onmessage — несет в себе то, ради чего вебсокеты и используют, т.е. для получения данных от сервера.

При этом загвоздка в том, что данные сервер обычно присылает разные, следовательно, нужны несколько разных onmessage? Или на каждую модель данных нужно создавать своё собственное соединение?

Итак, задача: нужно принять от сервера модель юзера и модель последних новостей, а может даже и еще чего другого.

Мне доводилось сталкиваться с подобной «изящной» реализацией:

На первый взгляд, всё логично. Но теперь представьте как это будет выглядеть, если их десятки или сотни. На одном из проектов, над которым мне довелось работать, событий было около трёх сотен.

Решаем проблему.

Все сторонние библиотеки для работы с вебсокетами позволяют подписываться на сообщения по типу addEventListener. Выглядит это примерно следующим образом:

Как мы знаем, оперировать мы можем одним единственном методом — onmessage, который и получает все данные в рамках своего соединения, поэтому такой код выглядит несколько необычно. Реализуется это следующим образом: onmessage возвращает MessageEvent, которое содержит поле data. Именно в data содержится информация, которую нам присылает сервер. Выглядит этот объект примерно следующим образом:

где event — это ключ, по которому можно определить, какую именно информацию прислал сервер. Далее на стороне фронтенда создается шина, которая фильтрует информацию по event и отсылает её по нужному адресу:

Это обеспечивает возможность в рамках одного соединения получать разные данные и подписываться на них через синтаксис, подобный обычным для JS событий.

WebSockets в Angular

Наконец мы подобрались к самому главному — использованию WebSockets непосредственно в Angular.

Несмотря на простоту работы с нативным WebSocket API, в этой статье мы будем использовать RxJs, что разумеется, ведь речь идет об Angular.

Нативный WebSocket API вполне можно использовать в приложениях на Angular, создать на его основе удобный в работе интерфейс, RxJs Observable, подписываться на нужные сообщения и т.д., но RxJs уже сделал за Вас основную работу: WebSocketSubject — это реактивная обертка над стандартным WebSocket API. Она не создает шину событий или обработку реконнекта. Это обычный Subject, при помощи которого можно работать с вебсокетами в реактивном стиле.

RxJs WebSocketSubject

Итак, WebSocketSubject ожидает WebSocketSubjectConfig и необязательный destination, в котором можно передать ссылку на свой наблюдаемый Subject, создает Observable, через который можно слушать и отправлять сообщения для вебсокетов.

Проще говоря, передаете в качестве аргумента WebSocketSubject url соединения и подписываетесь обычным для RxJs способом на всю активность вебсокета. А если нужно отослать сообщение серверу, то используете такой же обычный метод webSocketSubject.next(data).

Делаем сервис для работы с WebSocket Angular

Кратко опишем что мы ожидаем от сервиса:

· Единый и лаконичный интерфейс;

· Возможность конфигурации на уровне подключения зависимостей DI;

· Возможность переиспользования;

· Типизация;

· Возможность подписываться на получение информации по ключу;

· Возможность прервать подписку;

· Отправку сообщений на сервер;

· Реконнект.

На последний пункт стоит обратить отдельное внимание. Реконнект, или организация переподключения к серверу, это первостепенный фактор при работе с вебсокетами, т.к. обрывы сети, падения сервера или другие ошибки, вызывающие обрыв коннекта способны обрушить работу приложения.

Важно учесть, что попытки переподключения не должны быть слишком частыми и не должны продолжаться до бесконечности, т.к. такое поведение способно подвесить клиент.

Приступим.

Первым делом создадим интерфейс конфига сервиса и модуль, который обеспечит возможность конфигурирования при подключении.

Я буду по возможности сокращать код, полную версию вы можете посмотреть тут.

Далее нам потребуется описать интерфейс сообщения вебсокета:

где event — ключ, а data, получаемая по ключу — типизированная модель.

Публичный интерфейс сервиса выглядит так:

Сервис имеет поля:

В конструкторе класса сервиса мы получаем объект WebSocketConfig, заданный при подключении модуля:

Сам метод коннекта прост:

Реконнект немного сложнее:

Метод on, он тоже предельно прост, тут даже комментировать нечего.

Метод send еще проще:

Вот и весь сервис. Как видно, основной объем кода пришелся на организацию реконнекта.

Давайте теперь посмотрим, как этим пользоваться. Подключим модуль WebsocketModule:

В конструктор компонента заинжектим сервис и подпишемся на сообщения от ‘messages’, отправим обратно на сервер текст:

Название событий удобнее вынести в константы или перечисление. Создаем где-нибудь файл websocket.events.ts и запишем в него:

Перепишем подписки при помощи созданного объекта WS:

В завершение

Вот, собственно, и всё. Это необходимый минимум, который нужно знать разработчику на Angular про WebSockets. Я надеюсь, что достаточно внятно осветил эту тему. Полную версию сервиса можно найти на GitHub. Вторая статья по теме здесь.

Пишите, заходите к нам в телеграм, ставьте звезды 😉

Я в Telegram: http://t.me/AlexDaSoul

Я на GitHub: https://github.com/AlexDaSoul

Сообщество Angular в Telegram: https://t.me/angular_ru

--

--