SSEメッセージフォーマット
エージェントサーバーからHyperGen iframeへHTMLフラグメントをストリーミングするためのServer-Sent Eventsワイヤーフォーマット。
SSEメッセージフォーマット
本セクションでは、エージェントサーバーからHyperGen iframeへHTMLフラグメントをストリーミングするために使用されるServer-Sent Events (SSE) ワイヤーフォーマットを定義します。フォーマットはWHATWG EventSource仕様に、HyperGen固有の規約を加えたものです。
Content-Typeと必須ヘッダー
エージェントサーバーのSSEエンドポイントは以下のヘッダーを返さなければなりません (MUST):
| ヘッダー | 必須値 | 目的 |
|---|---|---|
Content-Type | text/event-stream | レスポンスをSSEストリームとして識別。正確に設定しなければならない (MUST)。 |
Cache-Control | no-cache, no-transform | ストリームのキャッシュと中間変換を防止。設定しなければならない (MUST)。 |
Connection | keep-alive | ストリーミングのためにTCP接続を維持。設定すべきである (SHOULD)。 |
X-Accel-Buffering | no | nginxリバースプロキシのバッファリングを無効化。設定すべきである (SHOULD)。 |
リバースプロキシ(nginx、Cloudflareなど)の背後にあるサーバーは、プロキシレイヤーでもレスポンスバッファリングを無効にすべきです (SHOULD)。バッファリングはプログレッシブストリーミングの目的を失わせます。
イベントフォーマット
各SSEイベントは1つ以上のフィールド行と空行(\n\n)で構成されます。HyperGenイベントはdata:フィールドに生HTMLを運びます。
dataフィールド
data:フィールドには生HTMLが含まれます — JSONラップもbase64エンコードもされていません。複数行のHTMLは複数のdata:行に分割しなければなりません (MUST)。EventSource APIがそれらを改行で連結します。
data: <div class="card">
data: <h2>Weather Report</h2>
data: <p>Tokyo: 18°C, partly cloudy</p>
data: </div>上記は1つのHTMLフラグメントを含む単一のSSEイベントです。末尾の空行がイベントを終了します。
イベント名
event:フィールドはSSEイベント名を指定します。HTMXのSSE拡張はこれを使用してsse-swap属性と照合します。
- デフォルトのイベント名は
messageです。event:フィールドが省略された場合、EventSource APIはイベントをmessageイベントとして扱います。 - サーバーはカスタムイベント名を使用して、iframe内の異なる要素をターゲットにしてもよいです (MAY)。
- ブートストラップドキュメントの
sse-swap属性は、サーバーが使用するイベント名と一致しなければなりません (MUST)。
event: message
data: <p>This targets the default sse-swap="message" element</p>event: sidebar
data: <nav>This targets an element with sse-swap="sidebar"</nav>イベントID
id:フィールドはオプション (OPTIONAL) です。存在する場合、ブラウザはそれを最後のイベントIDとして保存し、再接続時にLast-Event-IDヘッダーで送信します。サーバーは再開可能なストリームにこれを使用してもよいです (MAY)。
id: 42
event: message
data: <p>Fragment 42</p>リトライ間隔
retry:フィールドはオプション (OPTIONAL) です。再接続間隔をミリ秒で指定します。接続が失われた場合、ブラウザはこの値を使用します。
retry: 5000デフォルトの再接続動作(通常3秒)が適切でない場合、サーバーはストリームの早い段階でretry:フィールドを送信すべきです (SHOULD)。
キープアライブ
長時間のSSE接続は、プロキシ、ロードバランサー、またはブラウザによって、長期間データが流れない場合に閉じられる可能性があります。これを防ぐため、サーバーは定期的にキープアライブコメントを送信すべきです (SHOULD)。
キープアライブはSSEコメント行(:で始まる)に空行が続くものです:
: keepalive- サーバーは15秒以下の間隔でキープアライブコメントを送信すべきです (SHOULD)。
- コメントテキスト(
keepalive)は慣例的なもので、意味はありません。任意のコメントで動作します。 - キープアライブコメントは、単一イベントの
data:行の間に現れてはなりません (MUST NOT)。
doneイベント
エージェントが現在のストリームのすべてのコンテンツ生成を完了した場合、サーバーは完了を示すdoneイベントを送信すべきです (SHOULD):
event: done
data:doneイベントのdata:フィールドは空であってもよいです (MAY)。data:フィールドには最終HTML(完了インジケーターなど)を含めてもよいです (MAY)。done送信後、サーバーは接続を閉じるべきです (SHOULD)。- クライアントは
doneイベントを使用してUIの状態を更新してもよいです (MAY)(ローディングインジケーターの非表示、入力フィールドの有効化など)。
iframe内でdoneイベントを処理するため、エージェントはsse-swap="done"の要素を含めることができます:
<div sse-swap="done" hx-swap="innerHTML">
<span class="hg-indicator">Generating...</span>
</div>doneイベントが到着すると、HTMXがそのdata:コンテンツをこの要素にスワップし、ローディングインジケーターを置き換えます。
ストリームライフサイクル
1. 接続
iframeのブートストラップドキュメントにはsse-connectを持つ要素が含まれます:
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML">
</div>HTMXがこの要素を処理すると、指定されたURLへのEventSource接続を開きます。接続はデフォルトでGETを使用します。
2. ストリーミング
エージェントがHTMLを生成すると、サーバーはSSEイベントを送信します。HTMXは各イベントを受信し、data:フィールドからHTMLを解析し、設定されたhx-swap戦略を使用してターゲット要素にスワップします。
イベント間で、サーバーは接続を維持するためにキープアライブコメントを送信します。
3. 完了
サーバーはdoneイベントを送信し接続を閉じることで完了を示します。ブラウザのEventSourceはデフォルトで再接続を試みます — 単一レスポンスストリームを生成するエージェントは自動再接続に依存すべきではありません (SHOULD NOT)。代わりに、doneイベントハンドラーが再接続を防ぐためにsse-connect属性を削除できます:
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML"
hx-on::sse-close="this.removeAttribute('sse-connect')">
</div>4. エラー
SSE接続が失敗した場合、ブラウザの組み込み再接続ロジックが起動します(retry:間隔が送信されていた場合はそれを尊重)。HTMXはhtmx:sseErrorイベントを発火し、ブートストラップドキュメントはこれを使用してhg-disconnected CSSクラスを切り替えます。
5. 再接続
EventSource APIは接続の喪失時に自動的に再接続します。サーバーがid:フィールドを送信していた場合、ブラウザは再接続リクエストにLast-Event-IDを含めます。サーバーはこれを使用して中断箇所からストリームを再開してもよいです (MAY)。
再開をサポートしないサーバーは、再接続時に新しいストリームを送信すべきです (SHOULD)。
完全なワイヤーフォーマット例
以下は、ワイヤー上に現れる完全なSSEバイトストリームです。各\nはリテラルの改行文字(0x0A)を表します。
retry: 5000\n
\n
event: message\n
data: <div class="card">\n
data: <h2>Analyzing your data...</h2>\n
data: <div class="hg-indicator">Working...</div>\n
data: </div>\n
\n
: keepalive\n
\n
event: message\n
data: <div class="card">\n
data: <h2>Analysis Complete</h2>\n
data: <p>Found 3 anomalies in your dataset.</p>\n
data: <button hx-post="/api/agent/action"\n
data: hx-vals='{"action":"show_details"}'\n
data: hx-target="closest .card"\n
data: hx-swap="outerHTML">\n
data: Show Details\n
data: </button>\n
data: </div>\n
\n
event: done\n
data: \n
\n例の読み方
retry: 5000は再接続間隔を5秒に設定。- 最初の
messageイベントはローディング状態を配信。 - キープアライブコメントが処理中の接続を維持。
- 2番目の
messageイベントがローディング状態を結果で置換(ルート要素のhx-swap="innerHTML"のため)。 doneイベントがエージェントの完了を示す。
CORSに関する考慮事項
エージェントサーバーがiframeコンテンツとは異なるオリジンにある場合、サーバーは適切なCORSヘッダーを含めなければなりません (MUST):
Access-Control-Allow-Origin: <iframe-origin>
Access-Control-Allow-Credentials: true資格情報(Cookie、認証ヘッダー)が必要な場合、サーバーはAccess-Control-Allow-Origin: *を使用すべきではありません (SHOULD NOT) — CORS仕様はこの組み合わせを禁止しています。