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 服务器,请使用 hx-ext="sse"
属性在该 HTML 元素上安装扩展,然后在元素中添加 sse-connect="<url>"
建立连接。
在设计您的服务器应用程序时,请记住 SSE 的工作原理与任何 HTTP 请求相同。虽然您在建立连接后无法向服务器发送任何消息,但您可以在请求中将参数与您的请求一起发送到服务器。因此,您不是在 https://my-server/chat-updates
上建立到服务器的 SSE 连接,也可以连接到 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 连接,然后在每次看到 chatter
事件时触发对 /chatroom
url 的 GET
请求。
如果 SSE Event Stream 意外关闭,浏览器应该尝试自动重新连接。但是,在极少数情况下,这不起作用,并且您的浏览器可能会挂起。此扩展在浏览器自动重新连接的基础上添加了自己的重新连接逻辑(使用 指数退避算法),因此您的 SSE 流始终尽可能可靠。
Htmx 包含一个用 Node.js 编写的演示 SSE 服务器,它将帮助您查看 SSE 的实际操作,并开始引导您自己的 SSE 代码。它位于 htmx 分发版的 /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>" | 在标签中添加一个新属性 sse-connect ,该属性指定 Event Stream 的 URL。此属性必须与 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.elt
- 设置 SSE 连接的元素。这是具有 sse-connect
属性的元素。detail.source
- EventSource 对象。htmx:sseError
当 SSE 连接无法建立时,将分发此事件。
detail.error
- 创建 EventSource 时发生的错误。detail.source
- EventSource。htmx:sseBeforeMessage
在将 SSE 事件数据替换到 DOM 中之前,将分发此事件。如果您不想替换,请对事件调用 preventDefault()
。此外,detail
字段是一个 MessageEvent - 这是 EventSource 在收到 SSE 消息时创建的事件。
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.elt
- 替换目标。