Server-sent events
Описание проблемы
Современные web-сервисы нуждаются в постоянном получении изменений со стороны сервера. Для решения этих задач часто используют:
- Long polling - постоянное опрашивание сервера клиентом. Плюсы: простота реализации. Минусы: множество запросов.
- WebSocket - устанавливается постоянное двунаправленное соединение с сервером. Плюсы: постоянное соединение для отправки и получения данных с сервера. Минусы: сложность реализации, может блокироваться антивирусами.
- Server-sent events - устанавливается постоянное однонаправленное соединение с сервером. Плюсы: постоянное соединение для получения данных с сервера. Минусы: если нет HTTPS , то максимум 6 открытых страниц.
Мы рассмотрим небольшой пример реализации сервера и клиента для Server-sent events.
Код примера: github.com
Server-sent events требования для клиента
Все современные браузеры поддерживают event-streeming, кроме всех версий Internet Explorer.
Так же, есть возможность использовать polyfill. Например: Yaffle/EventSource
Формат работы
SSE определяет определённый формат сообщения:
retry: 5000
event: ping
data: {"message":"ping"}
retry
- количество миллисекунд для новой попытки подключения к серверу (в случае разрыва соединения). Достаточно отправить только в первом сообщении (при подключении клиента). По умолчанию: 3000.
event
- тип сообщения. Параметр необязательный. Если не указано, то отловить сообщение на клиенте возможно подписавшись на событие eventSource.onmessage = function (event) {...}
data
- полезная нагрузка. После всегда должна идти пустая строка.
При подключении отправляется запрос на сервер с заголовками connection: keep-alive
и content-type: text/event-stream
. Все оповещения от сервера так же будут видны в этом запросе.
Клиент
Для соединения с сервером используется объект EventSorsing (см. Код клиента).
Подробное описание объекта: Описание объекта. Опишу основные моменты.
- Подключение
const eventSource = new EventSource('http://localhost:8080/subscribe', {withCredentials: false});
EventSource принимает url подключения к серверу и объект с настройками соединения. withCredentials - передавать ли авторизационные данные(COOKIE и Authorization заголовки).
Если withCredentials: true, то сервер обязан вернуть:
Access-Control-Allow-Origin: https://sse.site.test
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
не будет работать с withCredentials: true.
- Подписка на определённые события
eventSource.addEventListener("ping", function (event) {
console.log(event.type + " " + event.data)
});
eventSource.addEventListener("example", function (event) {
const data = JSON.parse(event.data)
log("Получено сообщение (" + event.type + "): " + data.message);
});
eventSource.addEventListener("example", function (event) {...})
- подписываемся на событие example.
Сервер
- Структуры
DTO для отправки клиенту (internal/message.go)
type Message struct {
Message string `json:"message"`
}
Структура подключения (internal/connect.go)
type Connect struct {
Id string
Event string
MessageCh chan *Message
PingCh chan string
DoneCh chan struct{}
}
Event
- тип события.
MessageCh
- канал для отправки сообщения подписанным клиентам.
PingCh
- канал для отправки сообщения при подключении клиента.
DoneCh
- канал для отключения клиента.
- Брокер сообщений
Хранит в себе всех подписчиков. Может добавлять, удалять, оповещать и пинговать подписчиков. В данном примере реализовано хранение пользователей в памяти. Для хранения подписчиков лучше использовать Redis.
Файл: internal/broker.go
- main.go
Предоставляет в точки входа: подписаться на событие, отправить сообщение.
Файл: cmd/main.go
Запрос для публикации сообщения
http://localhost:8080/publish?message=new
Запуск
$ make init
- Открыть
public/client.html
в браузере. - Подписаться
- Отправить запрос на публикацию сообщения:
http://localhost:8080/publish?message=new
Подробнее:
- Server-sent events
- Использование отправляемых сервером событий
- Существующие на рынке решения: mercure
- Код примера: github.com