События сервера для мобильных web-приложений

Некоторое время назад возникла потребность реализовать в своём мобильном web-приложении оповещение клиента (фронт) о событиях, происходящих на сервере (бэк). Я рассматривал технологию Server Sent Events, как более простую альтернативу Websockets ("HTTP & text" vs. "TCP & binary").

Особенностями мобильных приложений (в том числе и мобильных web-приложений) является то, что они, зачастую, работают в условиях постоянного переподключения к интернету (например, переключение с мобильного подключения на wifi-подключение или переключение устройства между различными wifi-точками). В таких условиях долгоживущим соединениям существовать сложно (а SSE и Websockets — это долгоживущие соединения).

Анализируя ситуацию, обнаружил такие вещи:

  • Сервер не всегда может определить, что клиент отвалился. В некоторых ситуациях сервер продолжает отправлять сообщения и сообщения безвозвратно пропадают в Сети.
  • Без помощи со стороны web-приложения у сервера нет возможности определить, это один браузер с одного мобильного устройства открыл два отдельных SSE-соединения или это два разных браузера с разных мобильных устройств открывают соединения из-за одной точки доступа.

У меня получились такие выводы:

  • Мобильное приложение при установлении SSE-соединений с сервером должно генерировать некий UUID, чтобы с его помощью сервер мог отличить, когда web-приложение подключается повторно после разрыва соединения, а когда подключаются разные web-приложения с одинаковыми внешними IP-адресами.
  • SSE лучше не использовать для доставки клиенту бизнес-данных (например, данных о товаре, добавленном в каталог), но использовать для доставки оповещения о том, что нужно запросить бизнес-данные (что в каталоге появился новый товар и что клиент может запросить данные о всех новых товарах). В этом случае, если какой-то сигнал не дойдёт то клиента, то пропущенный товар попадёт к клиенту при обработке следующего сигнала. Т.е., бизнес-логика строится из расчёта, что по SSE сервер отправляет (push) сигналы клиенту, а клиент запрашивает (pull) данные, связанные с сигналами, отдельными запросами.
  • IMHO, желательно, чтобы клиент отдельным HTTP-запросом отправлял на сервер подтверждение каждому полученному от него сообщению. В таком случае сервер получает шанс закрыть соединение, если клиент ушёл в offline (если подтверждение о получении сообщения не пришло в течение определённого timeout’а).

Более подробно — здесь.

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

11
4 комментария

> Без помощи со стороны web-приложения у сервера нет возможности определить, это один браузер с одного мобильного устройства открыл два отдельных SSE-соединения или это два разных браузера с разных мобильных устройств открывают соединения из-за одной точки доступа.

Что-то мне здесь стало страшно за безопасность. Механизм COOKIE давно придуман (это то, что вы называете далее "некий UUID"), ну а здесь это всё же Application ID. и идеально ему не доверять, и каждое мобильное приложение перед началом работы получает свой ID от сервера (ID очень "сложный", по сути в нем и логин и пароль)

Синхронизация между двумя (хорошо, если их две) удаленными точками может реализоваться достаточно просто списком событий (просто данные); тем самым можем допустить работу приложения какое-то время в офлайне (например, закидывать продукты в корзину; а сервер потом в пакете обновления "скажет", что этого нет, здесь уже другая цена - приложение, соответственно реагирует). Идентификатор обязательно последовательный - так контролируем пропуски. У каждого есть очередь на отправку и статус подтверждения получения. Если важно время - становится сложнее, но иногда достаточно окончания "транзакции" - т.е. есть сообщение до которого все предыдущие события должны быть корректно обработаны и о них можно забыть.

1

Вы правы, можно использовать механизм cookies. Но нужно предварительно "посадить" куку на клиента. К тому же в качестве EventSource'а может выступать не тот сервер, с которого закачивались исходники фронта (и ставилась кука):
```
const source = new EventSource('http://domain.com/path/to/sse/:id');
```
в этом случае альтернатив GET-переменной я не вижу.

каждое мобильное приложение перед началом работы получает свой ID от сервера (ID очень "сложный", по сути в нем и логин и пароль)

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

тем самым можем допустить работу приложения какое-то время в офлайне ... а сервер потом в пакете обновления "скажет" ...

Тут другое направление, от бэка к фронту. Это бэк какое-то время работает в "оффлайн", отправляя данные на фронт, которые фронт не получает, т.к. отвалился. И у сервера нет возможности самостоятельно узнать, это надолго или навсегда.