# 機能設計書 74-静的ファイル配信

## 概要

本ドキュメントは、Etherpadにおける静的ファイル配信機能の設計を定義する。CSS、JavaScript、画像などの静的リソースの配信とMinify（圧縮）処理について記述する。

### 本機能の処理概要

Etherpadのフロントエンドに必要な静的ファイル（JavaScript、CSS、画像、フォントなど）を効率的に配信する機能である。ファイルのMinify処理、キャッシュ制御、MIMEタイプ設定を行う。

**業務上の目的・背景**：Webアプリケーションのパフォーマンスを向上させるため、静的ファイルの圧縮・キャッシュ管理が必要である。本機能により、転送データ量の削減、ページ読み込み速度の向上、サーバー負荷の軽減を実現する。また、プラグインの静的ファイルも同一のメカニズムで配信する。

**機能の利用シーン**：
- パッド編集画面のJavaScript/CSSファイル読み込み
- タイムスライダー画面のリソース読み込み
- プラグインの静的ファイル配信
- フォント、画像ファイルの配信

**主要な処理内容**：
1. HTTPリクエストの受信とパス解析
2. パスのサニタイズによるセキュリティ検証
3. ファイル存在確認と最終更新日時の取得
4. キャッシュヘッダーの設定（Last-Modified、Cache-Control、Expires）
5. 条件付きリクエスト（If-Modified-Since）の処理
6. JavaScript/CSSファイルのMinify処理（esbuildを使用）
7. MIMEタイプの設定とレスポンス送信

**関連システム・外部連携**：esbuildライブラリ（JavaScript/CSS圧縮）、mime-typesライブラリ（MIMEタイプ判定）を使用。

**権限による制御**：静的ファイルは認証なしで配信される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | トップページ | 補助機能 | JavaScript/CSSファイルの配信 |
| 2 | パッド編集画面 | 主機能 | パッド関連の静的ファイル配信 |
| 3 | タイムスライダー画面 | 補助機能 | タイムスライダー関連ファイル配信 |
| 7 | プラグイン管理画面 | 補助機能 | 管理画面用静的ファイル配信 |

## 機能種別

ファイル配信 / キャッシュ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| filename | string | Yes | リクエストされたファイルパス | サニタイズ処理で検証 |
| If-Modified-Since | string | No | 条件付きリクエストヘッダー | HTTPヘッダー形式 |

### 入力データソース

- HTTPリクエストURL（/static/* パス）
- リクエストヘッダー
- ファイルシステム

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Content-Type | string | ファイルのMIMEタイプ |
| Last-Modified | string | ファイルの最終更新日時 |
| Cache-Control | string | キャッシュ制御ディレクティブ |
| Expires | string | キャッシュ有効期限 |
| body | Buffer/string | ファイルコンテンツ（圧縮済みの場合あり） |

### 出力先

- HTTPレスポンス

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ /static/* パスのマッチング
2. パスサニタイズ
   └─ sanitizePathname()でセキュリティ検証
3. プラグインパス解決
   └─ plugins/* パスを実際のパスに変換
4. ファイル存在確認
   └─ statFile()で存在確認と最終更新日時取得
5. キャッシュ判定
   └─ If-Modified-Sinceヘッダーとの比較
6. Minify処理
   └─ JavaScript/CSSの場合は圧縮
7. レスポンス送信
   └─ 適切なヘッダーとコンテンツを送信
```

### フローチャート

```mermaid
flowchart TD
    A[/static/*リクエスト] --> B[sanitizePathname]
    B --> C{サニタイズ成功?}
    C -->|No| D[404 Not Found]
    C -->|Yes| E[プラグインパス解決]
    E --> F[statFile]
    F --> G{ファイル存在?}
    G -->|No| D
    G -->|Yes| H{If-Modified-Since >= Last-Modified?}
    H -->|Yes| I[304 Not Modified]
    H -->|No| J{HEAD/GET?}
    J -->|HEAD| K[200 + ヘッダーのみ]
    J -->|GET| L[getFileCompressed]
    L --> M{Minify有効 & JS/CSS?}
    M -->|Yes| N[esbuildで圧縮]
    M -->|No| O[そのまま返却]
    N --> P[200 + コンテンツ]
    O --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-74-01 | Minify有効化 | settings.minify=trueの場合にJS/CSSを圧縮 | JavaScriptまたはCSSファイル |
| BR-74-02 | キャッシュ期間 | settings.maxAgeで指定された期間をキャッシュ | 全ての静的ファイル |
| BR-74-03 | プラグインパス解決 | plugins/{name}/static/*を実際のパスに変換 | プラグイン静的ファイル |
| BR-74-04 | ホワイトリスト | node_modules内の特定ライブラリのみアクセス許可 | LIBRARY_WHITELIST定義 |
| BR-74-05 | 条件付きレスポンス | If-Modified-Since >= Last-Modifiedなら304 | 条件付きリクエスト |
| BR-74-06 | コンテンツキャッシュ | Minify結果をメモリにキャッシュ | contentCache Map |

### 計算ロジック

- **Expiresヘッダー**: Date.now() + settings.maxAge * 1000
- **Cache-Control**: max-age=${settings.maxAge}

## データベース操作仕様

本機能ではデータベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | ファイルが存在しない | エラーページ表示 |
| 404 | Not Found | パスサニタイズ失敗 | エラーページ表示 |
| 405 | Method Not Allowed | HEAD/GET以外のメソッド | 許可メソッドをヘッダーで通知 |

### リトライ仕様

なし

## トランザクション仕様

なし

## パフォーマンス要件

- Minify処理結果はメモリにキャッシュ
- デフォルトのmaxAgeは6時間（21600秒）
- esbuildによる高速なMinify処理

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

- sanitizePathname()によるディレクトリトラバーサル防止
- プラグインパスは登録済みプラグインのみ許可
- node_modulesはホワイトリストのライブラリのみ許可
- 機密ファイルは静的ファイルディレクトリに配置しないこと

## 備考

- esbuildはesbuildパッケージをJavaScript/CSS圧縮に使用
- CSS内の画像/フォントはdata URLとしてインライン化
- サーバー再起動時にcontentCacheはクリアされる

---

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

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

### 推奨読解順序

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

静的ファイル配信の設定とキャッシュ構造を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Settings.ts | `src/node/utils/Settings.ts` | minify、maxAge設定の型と初期値 |
| 1-2 | Minify.ts | `src/node/utils/Minify.ts` | contentCache Mapの定義 |

**読解のコツ**: settings.minifyとsettings.maxAgeがどのように使われるかを追跡する。

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

Expressルーティングの設定を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | static.ts | `src/node/hooks/express/static.ts` | /static/*ルートの設定 |

**主要処理フロー**:
1. **38行目**: app.all('/static/*filename', minify) でMinifyミドルウェアを登録
2. **43-56行目**: /pluginfw/plugin-definitions.json の配信

#### Step 3: Minify処理を理解する

メインのファイル配信ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Minify.ts | `src/node/utils/Minify.ts` | _minify関数の実装 |

**主要処理フロー**:
- **148-233行目**: _minify関数の全体
- **150-158行目**: パスサニタイズ
- **173-195行目**: プラグインパス解決
- **201-211行目**: キャッシュヘッダー設定
- **213-232行目**: 条件付きリクエスト処理とレスポンス送信

#### Step 4: ファイル圧縮処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Minify.ts | `src/node/utils/Minify.ts` | getFileCompressed関数 |
| 4-2 | MinifyWorker.ts | `src/node/utils/MinifyWorker.ts` | compressJS/compressCSS関数 |

**主要処理フロー**:
- **266-314行目**: getFileCompressed関数
- **MinifyWorker.ts 12-14行目**: JavaScriptのMinify（esbuild transform）
- **MinifyWorker.ts 21-42行目**: CSSのMinify（esbuild build）

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

```
Expressアプリケーション
    │
    └─ static.ts (expressPreSession hook)
           │
           └─ app.all('/static/*filename', minify)
                  │
                  └─ Minify.ts _minify()
                         ├─ sanitizePathname() [150-158行目]
                         ├─ プラグインパス解決 [173-195行目]
                         ├─ statFile() [201行目]
                         └─ getFileCompressed() [224行目]
                                │
                                ├─ getFile() [316-318行目]
                                │
                                └─ MinifyWorker.ts
                                       ├─ compressJS() [12-14行目]
                                       └─ compressCSS() [21-42行目]
```

### データフロー図

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

GET /static/js/pad.js ──▶ sanitizePathname() ──▶ js/pad.js
                                 │
                                 ▼
                          statFile()
                                 │
                                 ▼
                          getFileCompressed()
                                 │
                    ┌────────────┴────────────┐
                    ▼                         ▼
             JS/CSS?                     その他?
                    │                         │
                    ▼                         ▼
        compressJS/compressCSS()        そのまま返却
                    │                         │
                    └────────────┬────────────┘
                                 ▼
                          contentCacheに保存
                                 │
                                 ▼
                    HTTPレスポンス（圧縮済みコンテンツ）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| static.ts | `src/node/hooks/express/static.ts` | ソース | Expressルーティング設定 |
| Minify.ts | `src/node/utils/Minify.ts` | ソース | 静的ファイル配信のメインロジック |
| MinifyWorker.ts | `src/node/utils/MinifyWorker.ts` | ソース | JS/CSS圧縮処理 |
| sanitizePathname.ts | `src/node/utils/sanitizePathname.ts` | ソース | パスサニタイズ |
| Settings.ts | `src/node/utils/Settings.ts` | ソース | minify、maxAge設定 |
| tar.json | `src/node/utils/tar.json` | 設定 | バンドル定義 |
