概觀
長期以來,Laravel 的廣播功能提供 Reverb、Pusher、Ably 等「WebSocket 原生」驅動,如今config/broadcasting.php 的 driver 又新增了 mercure 這個選項。
Mercure 是以 Server-Sent Events (SSE) 為基礎的即時通訊協定。它不像 WebSocket 那樣採用雙向連線,而是接近 HTTP/1.1 或 HTTP/2 上的長輪詢,因此具有以下特點:
- 可以透明地穿越一般的 HTTP 基礎設施(反向代理、CDN、負載平衡器)
- 客戶端只需瀏覽器的
EventSourceAPI 就能實作,不需要專屬客戶端函式庫 - FrankenPHP 內建 Mercure hub,不需要額外基礎設施即可運作
新增的 driver 值
config/broadcasting.php 開頭列出支援驅動的註解中,新增了 mercure:
url,驅動會自動退回使用 FrankenPHP 內建的 Mercure hub(即 mercure_publish() 函式)。此判斷位於 CreatesMercureDrivers::mercure():
url 應指向用於發布的管理 API,而 public_url 則是瀏覽器所看到的 URL。之所以分開這兩者,是為了支援「透過內部網路(例如 Docker Compose)發布訊息,而只把公開 URL 暴露給瀏覽器」這類部署方式。
分離 publish 與 subscribe 兩種權杖
Mercure 是以 JWT (JSON Web Token) 進行存取控制的協定。CreatesMercureDrivers trait 會為 publish(伺服器 → hub)與 subscribe(瀏覽器 → hub)分別建立獨立的權杖工廠:
secret、publish_secret、subscribe_secret可以個別設定,未設定時會退回使用secretalgorithm、publish_algorithm、subscribe_algorithm同樣可個別設定(預設HS256)- HS256 要求 secret 至少 32 bytes、HS384 至少 48 bytes、HS512 至少 64 bytes,未達標會拋出
InvalidArgumentException
*)的 Grant::ACTION_PUBLISH 權限,並會由 CachingTokenProvider 記憶快取,避免每次廣播都要重新產生 JWT。
以單一 Cookie 進行頻道授權
Mercure 與 WebSocket 驅動最大的架構差異,在於訂閱端的授權是透過單一 Cookie 完成的。MercureBroadcaster::auth() 會接收 channel_names 陣列(每個請求上限 100 個),逐一評估頻道授權,然後一次發出一個涵蓋所有頻道的授權 Cookie:
denied: true,其他已授權的頻道仍可繼續訂閱。這樣的設計是因為 Mercure 在單一 EventSource 連線上多工承載多個 topic,所以要能讓頻道在連線中途新增或移除,同時不中斷既有的連線。
Presence 頻道會發出與一般 private / private-encrypted 頻道不同形狀的 Grant,其 subscribe 權限對應到 Mercure Subscription API 所使用的訂閱 URL 樣式。
端對端加密頻道
以private-encrypted- 為前綴的頻道會被視為端對端加密(E2EE)頻道 —— 也就是說,連 Mercure hub 本身都看不到 payload。這是既有 Pusher 與 Reverb 驅動所沒有的能力。
只要將 encryption_key 設定為 base64 編碼的 32 bytes 金鑰,ChannelEncrypter 就會啟用,並在 broadcast() 送出訊息時,將事件名稱、payload 與 socket ID 一併封裝成 JWE(JSON Web Encryption)。hub 中繼的只會是加密後的位元組資料。
auth() 回應中攜帶的 jwk(JSON Web Key)欄位,在瀏覽器內完成解密。這是 Mercure 規範建議的「將金鑰放在授權回應中以帶外方式傳遞」機制,hub 完全不會知道金鑰內容。
Presence 頻道無法加密,因為成員列表本來就需要透過 hub 的 Subscription API 流動,這在設計上就與 E2EE 不相容。
Whisper(客戶端之間的直接訊息)
當client_events 啟用時(預設為 true),每個受保護的頻道都會被指派一個專屬的「whisper topic」,訂閱者僅能對該 topic 擁有 publish 權限:
Topic 命名規則
由於 Mercure hub 常常被多個應用共用,因此 topic 會以topic_prefix 加上命名空間以避免衝突。預設值如下:
.alt 是 DNS 中保留、無法解析的網域字尾(RFC 9476),因此絕不會與真實網域衝突。頻道名稱會依 RFC 3986 進行 URL 編碼,並收納在單一路徑區段中:
Cookie 網域與錯誤訊息
Mercure hub 常常會與應用執行在不同的子網域上(例如mercure.example.com 與 app.example.com),因此當找不到共用的 Cookie 網域時,會拋出方便定位問題的例外訊息:
__Secure- 或 __Host- 前綴的 Cookie 名稱,驅動也會在啟動時檢查 public_url 是否為 HTTPS:
如何在 Reverb、Pusher/Ably 與 Mercure 之間取捨
Mercure 適合不需要持續雙向連線的情境 —— 例如通知、進度更新,或以伺服器對客戶端為主的類聊天應用。若你已經使用 FrankenPHP,更是完全不需要額外的基礎設施。
相關頁面
- 廣播基礎
- Laravel Cloud(以 FrankenPHP 為基礎的執行環境)