伺服器發送事件 (Server Sent Events)
擴充功能直接從 HTML 連接到 EventSource。它管理與您 Web 伺服器的連線,監聽伺服器事件,然後將其內容即時交換到您的 htmx 網頁中。
SSE 是 WebSockets 的輕量級替代方案,它透過現有的 HTTP 連線工作,因此很容易透過 Proxy 伺服器和防火牆使用。請記住,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/htmx-ext-sse@2.2.2/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
的伺服器,還可以連線到 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-swap 新增至將透過 SSE 擴充功能交換的任何元素。此屬性必須放置在包含 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
- 交換目標。