# 帳票設計書 7-EventStream

## 概要

本ドキュメントは、Laravel FrameworkにおけるEventStream（イベントストリーム）機能の設計仕様を定義する。サーバー送信イベント（SSE: Server-Sent Events）形式で出力する機能であり、リアルタイム帳票更新に利用可能。ResponseFactory::eventStream()で利用可能。

### 本帳票の処理概要

EventStreamは、HTTP Server-Sent Events（SSE）プロトコルを使用して、サーバーからクライアントへリアルタイムにデータをプッシュする機能を提供する。ジェネレータ関数を使用してイベントを順次送信し、ブラウザのEventSource APIと連携してリアルタイム更新を実現する。

**業務上の目的・背景**：モダンなWebアプリケーションでは、ダッシュボードの自動更新、進捗状況の表示、AIレスポンスのストリーミング、リアルタイム通知など、サーバーからクライアントへのプッシュ配信が必要になることが多い。EventStreamを使用することで、WebSocketよりも軽量でHTTP互換の方法でリアルタイム通信を実現できる。

**帳票の利用シーン**：
- AIチャットボットの回答ストリーミング
- 長時間処理の進捗状況表示
- リアルタイムダッシュボードの更新
- システムモニタリング画面のライブ更新
- ファイルアップロード進捗の表示

**主要な出力内容**：
1. SSE形式のイベントデータ（event: / data: 形式）
2. Content-Type: text/event-stream ヘッダー
3. ストリーム終了シグナル（endStreamWith）
4. カスタムイベント名のサポート

**帳票の出力タイミング**：ResponseFactory::eventStream()メソッドの呼び出し時にSSEストリームが開始され、ジェネレータからyieldされるデータがリアルタイムでクライアントに送信される。

**帳票の利用者**：アプリケーション開発者、リアルタイム機能を利用するエンドユーザー

## 帳票種別

Server-Sent Events / リアルタイムストリーム

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | リアルタイム更新画面 | 開発者定義 | Response::eventStream()呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | text/event-stream |
| 用紙サイズ | - |
| 向き | - |
| ファイル名 | - |
| 出力方法 | HTTPストリーミング（SSE） |
| 文字コード | UTF-8 |

### EventStream固有設定

| 項目 | 内容 |
|-----|------|
| Content-Type | text/event-stream |
| Cache-Control | no-cache |
| X-Accel-Buffering | no |
| デフォルト終了シグナル | \</stream\> |

## 帳票レイアウト

### レイアウト概要

EventStreamはSSE形式でデータを出力する。各イベントは`event:`と`data:`行で構成される。

```
┌─────────────────────────────────────┐
│          HTTPヘッダー部              │
│    Content-Type: text/event-stream  │
├─────────────────────────────────────┤
│    event: update                    │
│    data: {"message": "Hello"}       │
│                                     │
│    event: update                    │
│    data: {"message": "World"}       │
│                                     │
│    event: update                    │
│    data: </stream>                  │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Content-Type | SSEコンテンツタイプ | 自動設定 | text/event-stream |
| 2 | Cache-Control | キャッシュ無効化 | 自動設定 | no-cache |
| 3 | X-Accel-Buffering | Nginx バッファリング無効 | 自動設定 | no |

### 明細部（イベントデータ）

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | event | イベント名 | StreamedEvent::$event | event: {name}\n | - |
| 2 | data | イベントデータ | コールバックからのyield | data: {data}\n\n | - |

### フッター部（終了シグナル）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 終了イベント | ストリーム終了を示す | $endStreamWith | event: update\ndata: \</stream\>\n\n |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| callback | イベントを生成するジェネレータ関数 | Yes |
| headers | 追加HTTPヘッダー | No |
| endStreamWith | ストリーム終了シグナル | No（デフォルト: '\</stream\>'） |

### ソート順

イベントはジェネレータからyieldされた順序で送信される。

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| - | - | - |

### 改ページ条件

ストリーミングのため、改ページ条件は適用されない。

## データベース参照仕様

### 参照テーブル一覧

EventStream自体はデータベースを参照しない。イベントデータの取得にデータベースを参照する場合は、コールバック関数内で処理する。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| - | - | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| - | - | - | イベントデータはJs::encode()でJSON変換 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[eventStream()呼び出し] --> B[stream()メソッド呼び出し]
    B --> C[ジェネレータループ開始]
    C --> D{connection_aborted?}
    D -->|Yes| E[ループ終了]
    D -->|No| F{messageはStreamedEvent?}
    F -->|Yes| G[event名とdataを抽出]
    F -->|No| H[デフォルトevent名'update'使用]
    G --> I{data型チェック}
    H --> I
    I -->|string/numeric| J[そのまま使用]
    I -->|その他| K[Js::encode()でJSON変換]
    J --> L[event行出力]
    K --> L
    L --> M[data行出力]
    M --> N[改行出力]
    N --> O[ob_flush/flush]
    O --> C
    E --> P{endStreamWithあり?}
    P -->|Yes| Q[終了イベント出力]
    P -->|No| R[完了]
    Q --> R
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 接続切断 | クライアントが切断 | - | connection_aborted()で検出しループ終了 |
| JSONエンコードエラー | エンコード不可能なデータ | JsonException | データ形式を確認 |
| タイムアウト | 長時間ストリーミング | Maximum execution time exceeded | set_time_limit()で調整 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定イベント数 | 制限なし（ストリーミングのため） |
| 目標レイテンシ | ミリ秒単位（リアルタイム） |
| 同時接続数上限 | サーバーリソースに依存 |

## セキュリティ考慮事項

- SSE接続の認証を適切に実装すること
- 機密データのストリーミング時は暗号化を検討
- CORS設定を適切に行うこと
- レート制限を実装してリソース枯渇を防ぐこと

## 備考

- StreamedEventクラスを使用してカスタムイベント名を指定可能
- デフォルトイベント名は'update'
- connection_aborted()でクライアント切断を検出
- ob_flush()とflush()で即座にクライアントに送信

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

StreamedEventクラスの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | StreamedEvent.php | `src/Illuminate/Http/StreamedEvent.php` | イベント名とデータの構造 |

**読解のコツ**: StreamedEventは単純なデータコンテナで、$eventと$dataの2つのプロパティを持つ。

#### Step 2: エントリーポイントを理解する

処理の起点となるResponseFactoryのeventStream()メソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ResponseFactory.php | `src/Illuminate/Routing/ResponseFactory.php` | eventStream()メソッドの実装 |

**主要処理フロー**:
1. **行123-184**: eventStream()メソッドの実装
2. **行131**: stream()メソッドを呼び出しSSE処理を委譲
3. **行133-137**: コールバックの反復処理とconnection_aborted()チェック
4. **行139-144**: StreamedEventの場合はevent名とdataを抽出
5. **行146-148**: 文字列・数値以外はJs::encode()でJSON変換
6. **行150-158**: event:とdata:行を出力し、ob_flush/flush
7. **行161-178**: endStreamWithが設定されている場合の終了処理
8. **行179-183**: SSE用ヘッダーの設定

#### Step 3: stream()メソッドとの関係を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ResponseFactory.php | `src/Illuminate/Routing/ResponseFactory.php` | stream()メソッド |

**主要処理フロー**:
- **行186-213**: stream()メソッド - eventStream()から呼び出される基盤

### プログラム呼び出し階層図

```
ResponseFactory::eventStream($callback, $headers, $endStreamWith)
    │
    └─ $this->stream(function() use ($callback, $endStreamWith) {...})
           │
           ├─ foreach ($callback() as $message)
           │      │
           │      ├─ connection_aborted() チェック
           │      │
           │      ├─ [$message instanceof StreamedEvent]
           │      │      ├─ $event = $message->event
           │      │      └─ $message = $message->data
           │      │
           │      ├─ [!is_string && !is_numeric]
           │      │      └─ Js::encode($message)
           │      │
           │      ├─ echo "event: $event\n"
           │      ├─ echo "data: $message\n\n"
           │      ├─ ob_flush()
           │      └─ flush()
           │
           └─ [filled($endStreamWith)]
                  ├─ echo "event: $endEvent\n"
                  ├─ echo "data: $endStreamWith\n\n"
                  ├─ ob_flush()
                  └─ flush()
```

### データフロー図

```
[入力]               [処理]                    [出力]

ジェネレータ関数 ───▶ ResponseFactory     ───▶ HTTPレスポンス
$callback              ::eventStream()         (text/event-stream)
    │                       │
    ▼                       ▼
yield $data           stream()
yield new StreamedEvent()    │
    │                       ▼
    └───────────▶    foreach ループ
                           │
                           ▼
                    echo "event: ...\n"
                    echo "data: ...\n\n"
                           │
                           ▼
                    ob_flush() / flush()
                           │
                           ▼
                    クライアント（EventSource API）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ResponseFactory.php | `src/Illuminate/Routing/ResponseFactory.php` | ソース | eventStream()メソッドの実装 |
| StreamedEvent.php | `src/Illuminate/Http/StreamedEvent.php` | ソース | イベントデータ構造 |
| Js.php | `src/Illuminate/Support/Js.php` | ソース | JSONエンコード |
| Response.php | `src/Illuminate/Support/Facades/Response.php` | ソース | Facadeクラス |
| StreamedResponse.php | `vendor/symfony/http-foundation/StreamedResponse.php` | ソース | Symfony基底クラス |
