htmx Server Sent Events (SSE) 扩展
Server Sent Events 扩展程序可直接从 HTML 连接到EventSource。它管理与你的 Web 服务器的连接,监听服务器事件,然后实时将其内容交换到你的 htmx 网页中。
SSE 是 WebSockets 的轻量级替代方案,可在现有 HTTP 连接上运行,因此可以轻松通过代理服务器和防火墙使用。请记住,SSE 是一种单向服务,因此一旦建立连接,你就无法向 SSE 服务器发送任何消息。如果你需要双向通信,那么你应该考虑使用WebSockets。
此扩展取代了 htmx 先前版本中内置的实验性 hx-sse 属性。如需从旧版本迁移的帮助,请参阅本页底部的迁移指南。
使用以下属性来配置 SSE 连接的行为方式:
- sse-connect="<url>" - SSE 服务器的 URL。
- sse-swap="<message-name>" - 要交换到 DOM 中的消息的名称。
- hx-trigger="sse:<message-name>" - SSE 消息还可以使用hx-trigger 属性触发 HTTP 回调。
- sse-close=<message-name> - 收到该消息后正常关闭 EventStream。如果你想要向最终会停止的客户端发送信息,这可能会有所帮助。
安装
<script src="https://unpkg.com/[email protected]/sse.js"></script>
用法
<div hx-ext="sse" sse-connect="/chatroom" sse-swap="message">
Contents of this box will be updated in real time
with every SSE message received from the chatroom.
</div>
连接到 SSE 服务器
要连接到 SSE 服务器,请使用 hx-ext="sse" 属性在该 HTML 元素上安装扩展,然后添加 sse-connect="<url>" 到该元素以建立连接。
在设计服务器应用程序时,请记住 SSE 的工作方式与任何 HTTP 请求一样。虽然建立连接后你无法向服务器发送任何消息,但你可以将参数与请求一起发送到服务器。因此,除了连接到 https://my-server/chat-updates 你还可以连接到 https://my-server/chat-updates?friends=true&format=detailed 。这允许你的服务器根据客户端的需求自定义其响应。
接收命名事件
SSE 消息由事件名称和数据包组成。消息中不允许包含其他元数据。以下是示例:
event: EventName
data: <div>Content to swap into your HTML page.</div>
我们将使用 sse-swap 属性来监听该事件并将其内容交换到我们的网页中。
<div hx-ext="sse" sse-connect="/event-source" sse-swap="EventName"></div>
请注意 EventName,服务器消息中的名称必须与属性中的 sse-swap 值匹配。你的服务器可以根据需要使用任意数量的不同事件名称,但请注意:浏览器只能侦听已明确命名的事件。因此,如果你的服务器发送了命名为ChatroomUpdate 的事件,但你的浏览器只侦听已命名的事件 ChatUpdate,则多余的事件将被丢弃。
接收未命名事件
SSE 消息也可以在没有任何事件名称的情况下发送。在这种情况下,浏览器将使用默认名称 message 代替。上面指定的相同规则仍然适用。如果你的服务器发送未命名的消息,那么你必须通过包含来监听sse-swap="message"。没有使用万能名称的选项。如下所示:
data: <div>Content to swap into your HTML page.</div>
<div hx-ext="sse" sse-connect="/event-source" sse-swap="message"></div>
接收多个事件
你还可以从单个 EventSource 监听多个事件(命名或未命名)。监听器必须是 1) 包含 hx-ext 和 sse-connect 属性的相同元素,或 2) 包含 hx-ext 和 sse-connect 属性的元素的子元素。
Multiple events in the same element
<div hx-ext="sse" sse-connect="/server-url" sse-swap="event1,event2"></div>
Multiple events in different elements (from the same source).
<div hx-ext="sse" sse-connect="/server-url">
<div sse-swap="event1"></div>
<div sse-swap="event2"></div>
</div>
触发服务器回调
当建立了服务器发送事件的连接后,子元素可以使用 hx-trigger 特殊语法 sse:<event_name> 来监听这些事件。当与 hx-get 或类似的语法结合使用时,将触发元素发出请求。
以下是一个例子:
<div hx-ext="sse" sse-connect="/event_stream">
<div hx-get="/chatroom" hx-trigger="sse:chatter">
...
</div>
</div>
此示例与端点 event_stream 建立 SSE 连接,然后每当看到事件时就会触发对url /chatroomchatter 的 GET。
自动重新连接
如果 SSE 事件流意外关闭,浏览器应该会尝试自动重新连接。然而,在极少数情况下,这不起作用,你的浏览器可能会挂起。此扩展在浏览器的自动重新连接之上添加了自己的重新连接逻辑(使用指数退避算法),以便你的 SSE 流始终尽可能可靠。
使用演示服务器测试 SSE 连接
htmx 包含一个用 Node.js 编写的演示 SSE 服务器,可帮助你了解 SSE 的运行情况,并开始引导你自己的 SSE 代码。它位于 htmx-extensions 存储库的 /test/ws-sse 文件夹中。查看 /test/ws-sse/README.md 以获取有关运行和使用测试服务器的说明。
从先前版本迁移
以前版本的 htmx 使用内置标记 hx-sse 来实现服务器发送事件。此代码已迁移到扩展中。以下是迁移到此版本所需采取的步骤:
旧属性 | 新属性 | 评论 |
---|---|---|
hx-sse="" | hx-ext="sse" | 使用该 hx-ext="sse" 属性将 SSE 扩展安装到任何 HTML 元素中。 |
hx-sse="connect:<url>" | sse-connect="<url>" | 向指定事件流 URL 的标记添加 sse-connect 新属性。此属性必须与 hx-ext 属性位于同一标记中。 |
hx-sse="swap:<EventName>" | sse-swap="<EventName>" | 向将通过 SSE 扩展进行交换的任何元素添加 sse-swap 新属性。此属性必须放置在包含 hx-ext 属性的标签上或标签内。 |
hx-trigger="sse:<EventName>" | 没有变化 | 任何 hx-trigger 属性都不需要更改。扩展将识别这些属性,并为任何以 sse: 为前缀的事件添加监听器 |
监听此扩展发送的事件
此扩展会分派多个事件。你可以像这样监听这些事件:
document.body.addEventListener('htmx:sseBeforeMessage', function (e) {
// do something before the event data is swapped in
})
每个事件对象都有一个包含事件详细信息的 detail 字段。
htmx:sseOpen
当 SSE 连接成功建立时,会分派此事件。
detail
- detail.elt - 设置了 SSE 连接的元素。这是具有该 sse-connect 属性的元素。
- detail.source - EventSource对象。
htmx:sseError
当无法建立 SSE 连接时,会触发此事件。
detail
- detail.error - 创建EventSource时发生的错误。
- detail.source - 事件源。
htmx:sseBeforeMessage
此事件在 SSE 事件数据交换到 DOM 之前触发。如果你不想交换,请调用preventDefault() 事件。此外,该 detail 字段是一个 MessageEvent - 这是EventSource 在收到 SSE 消息时创建的事件。
detail
- detail.elt- 交换目标。
htmx:sseMessage
在 SSE 事件数据交换到 DOM 后,将调度此事件。该 detail 字段是一个MessageEvent - 这是 EventSource 在收到 SSE 消息时创建的事件。
htmx:sseClose
此事件在三种不同的关闭场景中分派。为了控制场景,用户可以控制 evt.detail.sseclose 属性。
document.body.addEventListener('htmx:sseClose', function (e) {
const reason = e.detail.type
switch (reason) {
case "nodeMissing":
// Parent node is missing and therefore connection was closed
...
case "nodeReplaced":
// Parent node replacement caused closing of connection
...
case "message":
// connection was closed due to reception of message sse-close
...
}
})
detail
- detail.elt - 交换目标。