Spec-Zone.ru › htmx

hx-sse

Передача HTML-потоком с помощью text/event-stream (SSE)

Расширение hx-sse позволяет передавать множество событий, отправляемых сервером (SSE) в одном HTTP-ответе.

Если вы использовали sse в htmx 2.0, см. примечания по миграции.

Установка

<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-sse.min.js"></script>

Использование

Обновление элемента

Начните с обычного запроса htmx с использованием hx-get:

<button hx-get="/ping">
  Ping
</button>

Вместо text/html ответьте потоком text/event-stream:

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: Pong

У этого события без имени есть поле data:, но нет поля event:.

Библиотеки для серверной части
  • Python: FastAPI или sse-starlette
  • Go: go-sse
  • PHP: потоки событий Laravel

Событие заменяет содержимое кнопки:

<button hx-get="/ping">
  Pong
</button>

htmx использует те же правила, что и для ответа text/html:

  • hx-target="this"
  • hx-swap="innerHTML" (из htmx.config.defaultSwap)

Передача обновления потоком

Также можно передавать HTML-потоком с помощью нескольких событий без имени:

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: P

data: Po

data: Pon

data: Pong

Кнопка изменяется по мере поступления событий:

Ping → P → Po → Pon → Pong

Выбор способа замены

Используйте hx-swap и hx-target, чтобы выбрать, как и где заменять содержимое при обновлении:

<button hx-post="/generate"
        hx-target="next output"
        hx-swap="beforeend">
  Generate
</button>

<!-- LLM tokens stream here -->
<output></output>

Каждое событие без имени содержит один текстовый фрагмент:

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: Hello

data: , world

data: !

hx-swap="beforeend" накапливает их в <output>:

<output>Hello, world!</output>

Также можно использовать:

  • hx-select, чтобы выбрать содержимое для замены
  • hx-select-oob, чтобы выбрать дополнительные элементы для замены

Обновление элементов

Используйте обычный запрос htmx, чтобы обновить несколько элементов:

<button hx-get="/events">Connect</button>

<div id="feed"></div>
<div id="status">Offline</div>

Сервер отправляет две дополнительные замены с помощью hx-swap-oob и <hx-partial>:

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: <div id="status" hx-swap-oob="true">Online</div>
data: <hx-partial hx-target="#feed"><p>New</p></hx-partial>

Страница становится такой:

<button hx-get="/events">Connect</button>

<div id="feed">
  <p>New</p>
</div>
<div id="status">Online</div>
Почему не использовалась обычная замена?

После извлечения дополнительных замен htmx обычная замена оказывается пустой:

(empty)

Элементы hx-swap-oob и <hx-partial> извлекаются до обычной замены. По умолчанию swapEmpty:false оставляет элемент подключения без изменений.

Сервер может сочетать дополнительные замены с обычным HTML:

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: <p>New event</p>
data: <hx-partial hx-target="#status">Busy</hx-partial>

Абзац следует за hx-target и hx-swap элемента запроса. Частичное обновление обновляет #status.

Чтобы отключить замену элемента запроса, установите hx-swap="none":

<button hx-get="/events" hx-swap="none">Connect</button>

Замены hx-swap-oob и <hx-partial> всё равно выполняются.

Постоянные подключения

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

Открытие подключений

Добавьте hx-sse:connect к элементу, который будет их получать:

<div hx-sse:connect="/events"></div>

Используйте hx-trigger, чтобы подключиться после события:

<button id="connect">Connect</button>

<div hx-sse:connect="/events"
     hx-trigger="click from:#connect">
</div>

Поддерживаются все модификаторы hx-trigger.

Закрытие подключений

Закройте подключение при получении события с определённым именем:

<div hx-sse:connect="/progress" hx-sse:close="done"></div>

Сервер отправляет:

HTTP/1.1 200 OK
Content-Type: text/event-stream

event: done
data: Complete

Обработчики на стороне клиента для события done выполняются до закрытия подключения.

Настройка подключений

Настроить hx-sse можно в трёх местах:

  • <meta name="htmx-config"> задаёт глобальные значения по умолчанию в HTML.

    <meta name="htmx-config"
          content="sse.reconnectDelay:1s sse.reconnectMaxAttempts:5">
  • htmx.config.sse задаёт глобальные значения по умолчанию в JavaScript.

    htmx.config.sse.reconnectDelay = '1s'
    htmx.config.sse.reconnectMaxAttempts = 5
  • hx-config переопределяет значения по умолчанию для одного подключения.

    <div hx-sse:connect="/events"
         hx-config="sse.reconnectMaxAttempts:2">
    </div>

Эти значения считываются при начале обработки потока.

Повторная отправка сообщений

Добавьте id:, чтобы получать сообщения, пропущенные во время отключения:

HTTP/1.1 200 OK
Content-Type: text/event-stream

id: event-42
data: <p>New message</p>

Поток сохраняет текущий идентификатор события:

  • Событие без id наследует текущий идентификатор.
  • Пустое id: очищает идентификатор.
  • Блок только с идентификатором, завершающийся пустой строкой, обновляет или очищает идентификатор, не отправляя сообщение.

При повторном подключении hx-sse включает Last-Event-ID, если текущий идентификатор не пуст:

Last-Event-ID: event-42

Это сообщает серверу, на каком месте остановился клиент, чтобы сервер мог повторно отправить пропущенные сообщения.

Server                          Client

event-42  ------------------->  received
event-43  --------X             disconnected
          <-------------------  Last-Event-ID: event-42
event-43  ------------------->  replayed

Сервер решает, какие сообщения отправлять повторно. hx-sse обрабатывает каждое полученное событие и не удаляет дубликаты повторно отправленных сообщений.

Вызов событий на стороне клиента

Поле event события SSE вызывает событие DOM вместо замены его данными:

HTTP/1.1 200 OK
Content-Type: text/event-stream

event: progress
data: 50
id: task-5

Обработайте его с помощью hx-on:

<button hx-get="/progress"
        hx-on:progress="htmx.find('#progress').value = event.detail.data">
  Start
</button>

<progress id="progress" max="100" value="0"></progress>

Событие всплывает от источника запроса и предоставляет:

event.detail = {
  data: '50',
  id: 'task-5'
}

Именованное событие также может инициировать другой запрос htmx:

<div hx-get="/status" hx-trigger="progress from:body"></div>

Атрибуты

hx-get/hx-post/hx-put/…

Расширение hx-sse расширяет возможности:

  • hx-get
  • hx-post
  • hx-put
  • hx-patch
  • hx-delete
  • hx-action с hx-method

Если в ответе используется Content-Type: text/event-stream, htmx обрабатывает каждое событие SSE по мере его поступления.

hx-sse:connect

Открывает постоянное SSE-подключение с помощью GET-запроса:

<div hx-sse:connect="/events"></div>

Подключение использует:

  • hx-headers и hx-vals для GET-запроса
  • hx-target, hx-swap, hx-select и hx-select-oob для каждого сообщения

Значения по умолчанию:

  • hx-trigger="load"
  • sse.reconnect:true
  • sse.pauseOnBackground:true
  • sse.releaseOn:immediate
  • swapEmpty:false для каждого сообщения

hx-sse:close

Закрывает подключение после события с соответствующим именем:

<div hx-sse:connect="/events" hx-sse:close="done"></div>
HTTP/1.1 200 OK
Content-Type: text/event-stream

event: done
data: Complete

Заголовки

Accept

Объявляет о поддержке SSE во всех запросах htmx, пока загружено расширение.

Accept: text/html, text/event-stream

Ответ передаётся потоком, если его Content-Type содержит text/event-stream.

Last-Event-ID

Указывает последнее полученное событие SSE при повторном подключении.

Last-Event-ID: event-42

Расширение отправляет этот заголовок, пока текущий идентификатор события не пуст. События без id наследуют этот идентификатор, а пустое id: удаляет заголовок при последующих подключениях.

Сервер решает, как повторно отправлять последующие сообщения. Клиент не удаляет дубликаты.

События

Данные события доступны в event.detail.

События подключения предоставляют:

event.detail.connection = {
  url,
  config,
  lastEventId,
  attempt,
  status,
  cancelled
}

События сообщений предоставляют:

event.detail = {
  connection,
  message: {
    data,
    event,
    id
  },
  waitUntil(), // before processing only
  cancelled    // before processing only
}

htmx:sse:before:connection

Вызывается перед тем, как htmx запускает начальный поток или планирует повторное подключение.

document.addEventListener('htmx:sse:before:connection', event => {
  if (event.detail.connection.attempt > 5) event.preventDefault()
})

Начальный HTTP-ответ уже получен. Отменить подключение можно любым из способов:

  • вызвать event.preventDefault()
  • установить event.detail.connection.cancelled в значение true

htmx:sse:after:connection

Вызывается, когда начальный ответ или повторное подключение готовы к передаче потока.

document.addEventListener('htmx:sse:after:connection', event => {
  console.log('Connected:', event.detail.connection.url)
})

connection.status содержит HTTP-статус.

htmx:sse:before:message

Вызывается перед обработкой сообщения SSE.

document.addEventListener('htmx:sse:before:message', event => {
  let message = event.detail.message
  if (message.event === 'heartbeat') event.preventDefault()
  else message.data = sanitize(message.data)
})

Изменение message.data меняет данные замены или именованного события. Изменение message.event меняет то, будет ли сообщение заменять содержимое или вызывать событие DOM.

detail.waitUntil(promise) откладывает обработку до завершения асинхронных операций. Отменить её можно с помощью event.preventDefault() или detail.cancelled = true.

htmx:sse:after:message

Вызывается после того, как htmx заменяет содержимое или вызывает событие SSE.

document.addEventListener('htmx:sse:after:message', event => {
  console.log('Received:', event.detail.message.data)
})

htmx:sse:close

Вызывается при закрытии потока SSE.

document.addEventListener('htmx:sse:close', event => {
  console.log('Closed:', event.detail.reason)
})

reason может иметь одно из следующих значений:

  • message: hx-sse:close совпало с именованным событием
  • removed: исходный элемент удалён из DOM
  • ended: поток завершился или исчерпал попытки повторного подключения
  • cancelled: начальное подключение к потоку отменено
  • cleanup: htmx очистил исходный элемент

htmx:sse:error

Вызывается при сбое чтения потока SSE или повторного подключения к нему.

document.addEventListener('htmx:sse:error', event => {
  console.error('SSE error:', event.detail.error)
})
  • connection: неудачное подключение, если оно доступно
  • url: URL SSE, если настройка завершилась ошибкой до создания подключения
  • error: значение ошибки
  • status: HTTP-статус неудачного ответа при повторном подключении, если он доступен

hx:release

Событие, отправляемое сервером, которое досрочно завершает жизненный цикл запроса. Полезно только вместе с sse.releaseOn:end.

event: hx:release
data:

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

Это полезно при потоковой передаче ответов LLM, когда нужно:

  • показывать индикатор загрузки, пока модель не начнёт отвечать
  • позволить пользователю взаимодействовать со страницей, пока продолжается передача токенов
<button hx-post="/generate"
        hx-target="#output"
        hx-swap="beforeend"
        hx-config="sse.releaseOn:end"
        hx-indicator="#spinner">
  Generate
</button>

Сервер передаёт поток:

data: First token

event: hx:release
data:

data: more tokens...

Индикатор скрывается после hx:release, но токены продолжают добавляться.

Конфигурация

sse.reconnect

Управляет автоматическим переподключением закрытого потока.

<meta name="htmx-config" content="sse.reconnect:false">

По умолчанию — true для hx-sse:connect и false для обычных запросов htmx.

sse.reconnectDelay

Задаёт время ожидания перед первой попыткой переподключения.

<meta name="htmx-config" content="sse.reconnectDelay:1s">

По умолчанию — 500 миллисекунд. После каждой неудачной попытки задержка удваивается. Значение можно указать в миллисекундах или в виде строки времени, например 500ms, 1s и 2m.

Сервер может переопределить это значение для потока:

HTTP/1.1 200 OK
Content-Type: text/event-stream

retry: 2000
data: Reconnect after two seconds

sse.reconnectMaxDelay

Ограничивает время ожидания между попытками переподключения.

<meta name="htmx-config" content="sse.reconnectMaxDelay:30s">

По умолчанию — 60000 миллисекунд. Укажите значение в миллисекундах или в виде строки времени.

sse.reconnectMaxAttempts

Ограничивает количество попыток переподключения закрытого потока.

<meta name="htmx-config" content="sse.reconnectMaxAttempts:5">

По умолчанию — Infinity.

sse.reconnectJitter

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

<meta name="htmx-config" content="sse.reconnectJitter:0">

По умолчанию — 0.3: задержка каждой попытки случайным образом изменяется не более чем на ±30%. Используйте 0 для точных задержек.

sse.pauseOnBackground

Закрывает поток, когда страница скрыта, и переподключается, когда она снова становится видимой.

<meta name="htmx-config" content="sse.pauseOnBackground:false">

По умолчанию — true для hx-sse:connect и false для обычных запросов htmx. Используйте идентификаторы событий и повторное воспроизведение на стороне сервера, чтобы получить сообщения, отправленные во время отключения.

sse.releaseOn

Управляет моментом завершения жизненного цикла запроса (индикаторы скрываются, элементы снова становятся активными).

<meta name="htmx-config" content="sse.releaseOn:first">

Значения:

  • immediate: завершить, когда SSE перехватывает управление (после получения заголовков)
  • first: завершить после подстановки первого сообщения
  • end: завершить, когда поток закроется

По умолчанию — immediate для hx-sse:connect и end для обычных запросов htmx.

При значении по умолчанию end индикаторы остаются видимыми, а hx-disable оставляет элементы отключёнными, пока поток не закроется. Это удобно для потоковой передачи от LLM, когда нужно предотвратить повторную отправку запросов.

Используйте first, если хотите, чтобы интерфейс стал интерактивным сразу после поступления контента:

<button hx-post="/generate"
        hx-config="sse.releaseOn:first">
  Generate
</button>

Сервер также может завершить обработку раньше, отправив событие hx:release:

event: hx:release
data:

Миграция

htmx 2.0

В htmx 2.0 использовался EventSource, а именованные сообщения выбирались с помощью sse-swap:

<div sse-connect="/chat" sse-swap="message"></div>

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

<div hx-sse:connect="/chat"></div>
HTTP/1.1 200 OK
Content-Type: text/event-stream

data: <p>New message</p>

Теперь именованные сообщения вызывают события DOM вместо выбора целевого элемента для подстановки:

HTTP/1.1 200 OK
Content-Type: text/event-stream

event: progress
data: 50
<div hx-sse:connect="/progress"
     hx-on:progress="updateProgress(event.detail.data)">
</div>

Чтобы запустить другой запрос, используйте обычное имя события:

<!-- htmx 2 -->
<div hx-get="/status" hx-trigger="sse:progress"></div>

<!-- htmx 4 -->
<div hx-get="/status" hx-trigger="progress from:body"></div>

Атрибуты

Изменились следующие атрибуты:

htmx 2.x htmx 4.x Совместимость
sse-connect hx-sse:connect Работает с предупреждением
sse-swap Неименованные сообщения подставляются автоматически Удалён; выводится предупреждение
sse-close hx-sse:close Работает с предупреждением

События

Изменились следующие события:

htmx 2.x htmx 4.x
htmx:sseOpen htmx:sse:after:connection
htmx:sseError htmx:sse:error
htmx:sseBeforeMessage htmx:sse:before:message
htmx:sseMessage htmx:sse:after:message
htmx:sseClose htmx:sse:close

В htmx 4 вместо EventSource используются fetch() и ReadableStream. Поэтому в запросах с ответами SSE можно использовать любые HTTP-методы htmx, значения запросов и заголовки.

От бета-версии до RC1

В RC1 события жизненного цикла SSE получили пространство имён:

Бета-версия RC1
htmx:before:sse:connection htmx:sse:before:connection
htmx:after:sse:connection htmx:sse:after:connection
htmx:before:sse:message htmx:sse:before:message
htmx:after:sse:message htmx:sse:after:message

Отмена обработки сообщения перенесена из detail.message.cancelled в detail.cancelled. Хуки перед обработкой сообщения могут отложить обработку с помощью detail.waitUntil(promise).

Licensed under the Zero-Clause BSD License.
https://four.htmx.org/extensions/hx-sse/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API