SSE или стримы на PHP#пост
SSE – Server-sent events. Стрим – потоковая передача данных в рамках одной сессии.
SSE – протокол общения сервера и клиента, когда сервер отдает информацию частично, при этом не обрывая и не завершая соединение.
Для простой реализации такого протокола потребуется:
- Добавить в HTTP Headers
Content-Type: text/event-stream- В HTTP Body писать следующее:
- Передача сообщения начинается с
data: - Заканчивается сообщение с
\n\n на конце строки
- Таких сообщений может быть неограниченное количество
- Задержка между сообщения может быть любая: секунда, минута, 30 минут, прочее
Рассмотрим несколько примеров с возможностями SSE:
- Так пишутся комментарии, с двоеточием вначале
: comment
: another comment
data: ...
- Кастомное имя события. Если не указать
event, то событие будет типа
message. В моем примере тип события =
question с телом в виде JSON
event: question
data: {"tg_channel":"@handle_topic"}
- Событие с
id. Не вижу в этом
id какой-то полезности для себя.
data: text message
id: 55
- Длинные строки. Это всё еще одно событие.
data: looooooooooooooooong text
data: looooooooooooooooong text
data: looooooooooooooooong text
- А это уже два разных сообщения:
data: payload1
data: payload2
- Еще можно указать
retry в milliseconds. Будет учтено при обрыве соединения и попытке восстановления. Например, 1 секунда (1000 миллисекунд).
data: ok
retry: 1000
Это довольно удобная штука, когда нужно что-то недолго* постримить пользователю:
- Прогресс бар для загрузки файлов
- Какое-то реалтайм действие
- Асинхронная операция и прочее
РеализацияРеализовать на чистом PHP довольно просто: посмотреть пример можно
здесь.
Однако, мало кто пишет на чистом пхп свои бекенды. Скорее всего на бекенде используется какой-то фреймворк с ответами в виде класса
Response. Либо сразу в контроллере, либо под капотом происходит трансформация возвращаемого значения в строковый ответ.
Если разбирать PSR
ResponseInterface , то HTTP Body формируется через метод
withBody, который принимает объект
StreamInterface. Ага, вот и стримы обещанные.
СтримыТак как задать несколько ответов с задержкой?
Раз уж наш ответ является стримом, то стрим не обязан возвращать все данные “за одно чтение”.
Поэтому можно сделать некий буфер, который будет вычитываться в методе
read() . Если данных больше не будет, то достаточно поменять флаг
eof и вернуть его в соответствующем методе объекта стрима.
Однако, набирать буффер и потом его сбрасывать может быть чревато разными проблемами:
- Бекенд будет думать, что клиент всё принял и обработал
- Клиент получит часть, что-то сломается, и получить остальную часть он уже не сможет
- Дополнительные затраты на память
Поэтому, можно сделать небольшой
sleep() в методе чтения стрима и производить обработку следующего события только после того, как первое было успешно выдано клиенту.
ОграниченияРазберем еще несколько ограничений SSE:
- Можно стримить только body
- Хедеры нельзя менять после начала стрима
- Кодировка всегда UTF-8
Emitter фреймворка должен уметь в стримы, чтобы не выдать первый результат и закрыть стрим, либо объект стрима должен сам уметь блокироваться и чанковать данные.
Проблемы- Сложная реализация
- Реализовывать стримминг событий на PHP при помощи SSE еще и в PSR обёртках?
- Проще подключить Mercure и настроить его.
- Протокол – пустышка
- Ни авторизации, ни разделения на каналы, ни бродкастинга
- Всё делать либо в HTTP, либо самому
- По сути в нем можно перегонять лишь текст и всё
- Это как HTTP внутри HTTP. Шутка про монитор в мониторе
- Свободные воркеры
- Свободные воркеры веб-сервера превращаются в зомби за мгновение
- Поэтому таймауты обязательны
- Либо Non-blocking I/O. Кстати, надо бы попробовать.
---
Полезные ссылки:MDN Server-sent eventsStreamInterfaceHTML Standard@handle_topic