Upgrade to Pro — share decks privately, control downloads, hide ads and more …

今だからこそ入門する Server-Sent Events (SSE)

今だからこそ入門する Server-Sent Events (SSE)

第131回 NearMe 技術勉強会で発表した資料です。
テーマは「今だからこそ入門する Server-Sent Events (SSE)」。
SSE の基本的な仕組み、HTTP/1.x 上での利用方法、実装例(pingライクなサンプルや ChatGPT風のストリーミング表示)を紹介しています。
フロントエンドとバックエンド双方のコード例を交えながら、WebSocket との違いや利点についても触れています。
SSE を使ったリアルタイム通信に興味がある方に向けた入門資料です。

More Decks by NearMeの技術発表資料です

Other Decks in Programming

Transcript

  1. 2 1. Server-Sent Events とは • Server-Sent Events(以下、SSE) ◦ HTTP/1.x

    でサーバ→クライアントの⼀⽅向ストリーム ◦ ブラウザAPI標準で最⼩実装が容易 • SSEの簡単な使い⽅ ◦ サーバー側にて 'Content-Type': 'text/event-stream' にしてデータ送信 ◦ クライアント側で new EventSource にてインスタンス化して、 addEventListener にて待ち受け 参考:https://developer.mozilla.org/ja/docs/Web/API/Server-sent_events/Using_server-sent_events const evtSource = new EventSource() evtSource.addEventListener("CUSTOM_EVENT", (event) => { ... });
  2. 3 1. Server-Sent Events とは • SSE のメッセージ形式 ◦ Content-Type:

    text/event-stream ◦ "data:" → これを最初においてメッセージを書く ◦ "event:" → イベント名をカスタマイズできる ◦ 区切る場合には空⾏を⽤いる 参考:https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events-intro HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive : heartbeat (comment line) retry: 1500 id: 1 event: system data: {"startedAt": 1736722330000} id: 2 data: これは1つめのチャンクです id: 3 data: 複数行のdataは data: 行ごとにdata:を繰り返します data: 最終行です event: done data: {"ok": true}
  3. 4 1. Server-Sent Events とは • SSE は現在多く利⽤されている ChatGPT の

    stream オプションでも利⽤されている 参考:https://platform.openai.com/docs/api-reference/realtime-beta-client-events/conversation/item/truncate#responses-create-stream
  4. 7 2. SSE の具体的な使い⽅ • 今回使う資料 ◦ https://github.com/asakaicode/sse-handson • 資料の内容

    1. 簡単な SSE 実装(pingライク) 2. ChatGPTのようなストリーミング表⽰(LLMにはまだ接続していません)
  5. 9 2. SSE の具体的な使い⽅:簡単な SSE 実装(pingライク) 2. テキストが1秒ごとに出⼒される   →

    “Disconnect” にて出⼒終了   → “Reconnect” にてリロードして最初から
  6. 10 2. SSE の具体的な使い⽅:簡単な SSE 実装(pingライク) • 仕組み(バックエンド) ◦ 以下のようにヘッダーを構成

    ▪ 'Content-Type', 'text/event-stream'としている app.get('/stream', (req: Request, res: Response) => { res.setHeader('Content-Type', 'text/event-stream; charset=utf-8') res.setHeader('Cache-Control', 'no-cache') res.setHeader('Connection', 'keep-alive') res.setHeader('Access-Control-Allow-Origin', '*')
  7. 11 2. SSE の具体的な使い⽅:簡単な SSE 実装(pingライク) • 仕組み(バックエンド) ◦ 以下のようにヘッダーを構成

    ▪ 'Content-Type', 'text/event-stream'としている ◦ 1秒ごとのインターバルでテキストを送信 const ping = setInterval(() => { const now = new Date().toISOString() res.write(`id: ${++id}\n`) res.write(`event: ping\n`) res.write(`data: {"time":"${now}"}\n\n`) }, 1000)
  8. 13 2. SSE の具体的な使い⽅:簡単な SSE 実装(pingライク) • 仕組み(フロントエンド) ◦ EventSource

    インスタンスを作成 ◦ ping イベントが来たときに画⾯にテキストを出⼒ const onPing = (e: MessageEvent) => { try { const payload = JSON.parse(e.data) setLogs((p) => [...p, `[ping] ${payload.time}`]) } catch { setLogs((p) => [...p, `[ping] ${e.data}`]) } } es.addEventListener('ping', onPing)
  9. 17 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(バックエンド) ◦ ヘッダーは先ほどと同様 ◦ まずはフロントエンドからメッセージ全体を受け取る

    app.post('/api/conversations', (req: Request, res: Response) => { const message = (req.body?.message ?? '').toString() if (!message) return res.status(400).json({ error: 'message is required' }) const id = nanoid() const text = craftReply(message) streams.set(id, { text, cursor: 0, timer: null }) res.json({ id }) })
  10. 18 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(バックエンド) ◦ ヘッダーは先ほどと同様 ◦ まずはフロントエンドからメッセージ全体を受け取る

    ◦ メッセージが終わるまではインターバルでテキストを送り続ける app.get('/api/conversations/:id/stream', (req: Request, res: Response) => { ... const chunk = words[item.cursor++] res.write(`data: ${JSON.stringify(chunk)}\n\n`) }, 40) })
  11. 19 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(バックエンド) ◦ ヘッダーは先ほどと同様 ◦ まずはフロントエンドからメッセージ全体を受け取る

    ◦ メッセージが終わるまではインターバルでテキストを送り続ける ◦ 全部送ったら ʻdone’ イベントとしてテキストを送る app.get('/api/conversations/:id/stream', (req: Request, res: Response) => { ... item.timer = setInterval(() => { if (item.cursor >= words.length) { res.write(`event: done\n`) res.write(`data: { "ok": true }\n\n`) ... })
  12. 20 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(フロントエンド) ◦ まずはバックエンドの /api/conversations エンドポイントにテキスト全体を送る

    const send = useCallback(async (text: string) => { ... try { const r = await fetch(`${API_BASE}/api/conversations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text }), }) const { id } = await r.json()
  13. 21 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(フロントエンド) ◦ まずはバックエンドの /api/conversations エンドポイントにテキスト全体を送る

    ◦ 先ほど受け取ったidを⽤いて EventSource インスタンスを作成 const send = useCallback(async (text: string) => { ... try { ... const es = new EventSource(`${API_BASE}/api/conversations/${id}/stream`)
  14. 22 2. SSE の具体的な使い⽅:ChatGPTのようなストリーミング表⽰ • 仕組み(フロントエンド) ◦ まずはバックエンドの /api/conversations エンドポイントにテキスト全体を送る

    ◦ 先ほど受け取ったidを⽤いて EventSource インスタンスを作成 ◦ メッセージを受け取り、’done’ イベントを受け取ったら終了する es.addEventListener('done', () => { es.close() ... setBusy(false) })