# 機能設計書 11-レートリミッター

## 概要

本ドキュメントは、Laravel FrameworkのレートリミッターRateLimiter機能の設計を詳細に記述したものである。APIリクエストの流量制御やDDoS対策のためのリクエスト回数制限機能を提供する。

### 本機能の処理概要

レートリミッターは、特定のキーに対するアクション実行回数を追跡・制限し、システムリソースの保護とAPI利用量の制御を実現する機能である。

**業務上の目的・背景**：Webアプリケーションにおいて、悪意のあるユーザーによる大量リクエスト攻撃DDoSや、API利用量の不正な超過を防止する必要がある。レートリミッターは、指定された時間枠内でのリクエスト回数を制限することで、サーバーリソースの保護とサービスの安定稼働を実現する。また、外部APIとの連携時に、相手先APIの利用制限を遵守するためにも活用される。

**機能の利用シーン**：
- ログイン試行回数の制限（ブルートフォース攻撃対策）
- APIエンドポイントへのリクエスト回数制限
- パスワードリセット要求の制限
- メール送信回数の制限
- ファイルアップロード回数の制限

**主要な処理内容**：
1. 名前付きリミッター設定の登録（for メソッド）
2. 試行回数の記録とカウント増加（hit, increment メソッド）
3. 制限超過チェック（tooManyAttempts メソッド）
4. 条件付きコールバック実行（attempt メソッド）
5. 制限解除までの残り時間取得（availableIn メソッド）
6. 試行回数・タイマーのリセット（clear, resetAttempts メソッド）

**関連システム・外部連携**：キャッシュシステム（Redis、Memcached、Database等）と連携し、試行回数とタイマー情報を永続化する。特にRedis使用時は、PhpRedisConnectionを通じてシリアライゼーション・圧縮なしの最適化された操作を行う。

**権限による制御**：レートリミッター自体は権限制御を持たないが、名前付きリミッター設定時にリクエストコンテキストに応じた動的な制限値の設定が可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | 429 Too Many Requests | 主画面 | リクエスト過多エラー表示（レート制限超過時） |

## 機能種別

バリデーション / 流量制御 / セキュリティ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| key | string | Yes | リミッターを識別するキー | Unicode文字はHTMLエンティティ変換後にクリーンアップ |
| maxAttempts | int | Yes | 最大試行回数 | 正の整数 |
| decaySeconds | int/DateTimeInterface/DateInterval | No | 制限期間（秒）、デフォルト60秒 | 正の数値またはDateTime系オブジェクト |
| callback | Closure | No | attempt時に実行するコールバック | callable |
| name | string/UnitEnum | Yes（for メソッド） | 名前付きリミッターの識別子 | enum_value変換対応 |

### 入力データソース

- 画面入力：ログインフォーム、API呼び出しからのリクエストパラメータ
- システム生成：IPアドレス、ユーザーID、セッションID等を組み合わせたキー

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| attempts | int | 現在の試行回数 |
| remaining | int | 残り試行可能回数 |
| availableIn | int | 制限解除までの秒数 |
| tooManyAttempts | bool | 制限超過しているか |
| attemptResult | mixed | attemptメソッドのコールバック戻り値またはfalse |

### 出力先

- キャッシュストア：試行回数カウンターとタイマー
- HTTPレスポンス：429 Too Many Requestsステータス（ミドルウェア経由）

## 処理フロー

### 処理シーケンス

```
1. リミッターキーの生成
   └─ cleanRateLimiterKey でUnicode文字をクリーンアップ
2. 現在の試行回数を取得
   └─ キャッシュから :timer 付きキーの存在確認
3. 制限チェック
   └─ attempts >= maxAttempts かつタイマーが有効なら制限中
4. 試行回数の増分
   └─ キャッシュに値をインクリメント、タイマー設定
5. コールバック実行（attemptメソッドの場合）
   └─ 制限内ならコールバック実行、結果を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[キーのクリーンアップ]
    B --> C{tooManyAttempts?}
    C -->|Yes| D[false を返却]
    C -->|No| E[コールバック実行]
    E --> F[hit でカウント増加]
    F --> G[タイマー設定]
    G --> H[結果を返却]
    D --> I[終了]
    H --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-11-01 | 試行回数上限 | 指定された最大試行回数を超えた場合、以降のリクエストを拒否する | decaySeconds期間内 |
| BR-11-02 | タイマーリセット | タイマー期限切れ後、カウンターは自動リセットされる | タイマー無効時 |
| BR-11-03 | 重複キー処理 | 複数のLimitオブジェクトで同一キーが存在する場合、fallbackKeyを使用 | limiterメソッド呼び出し時 |

### 計算ロジック

```php
// 残り試行回数の計算
remaining = max(0, maxAttempts - attempts)

// 制限解除までの秒数
availableIn = max(0, cache.get(key.':timer') - currentTime())
```

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

### 操作別データベース影響一覧

レートリミッターは直接データベースを操作しない。キャッシュドライバとしてDatabaseを使用する場合のみ、キャッシュテーブルへの操作が発生する。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 試行記録 | cache（キャッシュドライバ依存） | INSERT/UPDATE | 試行回数とタイマーの記録 |
| 試行取得 | cache（キャッシュドライバ依存） | SELECT | 現在の試行回数取得 |
| リセット | cache（キャッシュドライバ依存） | DELETE | 試行回数とタイマーの削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 429 | Too Many Requests | tooManyAttemptsがtrueを返却 | Retry-Afterヘッダーと共に429レスポンス |
| - | CacheException | キャッシュ接続エラー | 例外をスローし上位で処理 |

### リトライ仕様

- Retry-Afterヘッダー：availableInメソッドで取得した秒数を設定
- クライアントは指定秒数後にリトライ可能

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

レートリミッターはトランザクションを使用しない。キャッシュの原子的操作（increment）に依存する。

## パフォーマンス要件

- キャッシュアクセスは1リクエストあたり最大3回（timer確認、値取得、increment）
- Redis使用時はwithoutSerializationOrCompressionで最適化
- 大量の同時リクエストに対応するため、原子的なincrement操作を使用

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

- IPベースの制限だけでなく、ユーザーIDやセッションと組み合わせたキー設計を推奨
- プロキシ経由のリクエストではX-Forwarded-Forヘッダーの考慮が必要
- 分散DoS対策として、上位のCDN/WAFとの併用を推奨

## 備考

- UnitEnum対応：PHP 8.1以降のenumを名前として使用可能
- InteractsWithTimeトレイト使用：時間計算の共通処理を提供

---

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

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

### 推奨読解順序

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

まず、レートリミッターが管理するデータの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | プロパティ定義（$cache, $limiters）を確認 |

**読解のコツ**: RateLimiterクラスは2つの主要プロパティを持つ。`$cache`はキャッシュストアへの参照、`$limiters`は名前付きリミッター設定のコールバック配列である。

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

主要なエントリーポイントであるメソッドを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | forメソッド（48-55行目）で名前付きリミッター登録 |
| 2-2 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | attemptメソッド（105-118行目）で条件付き実行 |

**主要処理フロー**:
1. **48-55行目**: `for` メソッドで名前付きリミッターを登録。名前とClosureのマッピングを`$limiters`配列に保存
2. **63-94行目**: `limiter` メソッドで登録済みリミッターを取得。重複キー処理も含む
3. **105-118行目**: `attempt` メソッドで制限チェック後にコールバック実行

#### Step 3: コアロジックを理解する

試行回数管理のコアロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | tooManyAttemptsメソッド（127-138行目）で制限チェック |
| 3-2 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | incrementメソッド（160-181行目）でカウント増加とタイマー設定 |
| 3-3 | RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | withoutSerializationOrCompressionメソッド（295-310行目）でRedis最適化 |

**主要処理フロー**:
- **127-138行目**: `tooManyAttempts` - 現在の試行回数が上限以上かつタイマーが有効かをチェック
- **160-181行目**: `increment` - キャッシュにタイマーと試行回数を設定、原子的インクリメント
- **284-287行目**: `cleanRateLimiterKey` - Unicode文字のサニタイズ処理

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

```
RateLimiter::attempt($key, $maxAttempts, $callback, $decaySeconds)
    │
    ├─ RateLimiter::tooManyAttempts($key, $maxAttempts)
    │      ├─ RateLimiter::attempts($key)
    │      │      └─ Cache::get($key)
    │      └─ Cache::has($key.':timer')
    │
    ├─ $callback()  // ユーザー定義処理
    │
    └─ RateLimiter::hit($key, $decaySeconds)
           └─ RateLimiter::increment($key, $decaySeconds, 1)
                  ├─ RateLimiter::cleanRateLimiterKey($key)
                  ├─ Cache::add($key.':timer', ...)
                  ├─ Cache::add($key, 0, ...)
                  └─ Cache::increment($key, 1)
```

### データフロー図

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

リクエストキー     ───▶  cleanRateLimiterKey()  ───▶  サニタイズ済みキー
                              │
maxAttempts        ───▶  tooManyAttempts()      ───▶  bool（制限中か否か）
                              │
decaySeconds       ───▶  increment()            ───▶  キャッシュ更新
                              │                          ├─ key: 試行回数
                              │                          └─ key:timer: 有効期限
callback           ───▶  attempt()              ───▶  コールバック結果 or false
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RateLimiter.php | `src/Illuminate/Cache/RateLimiter.php` | ソース | レートリミッターコア実装 |
| RateLimiter.php | `src/Illuminate/Support/Facades/RateLimiter.php` | ソース | Facadeクラス |
| InteractsWithTime.php | `src/Illuminate/Support/InteractsWithTime.php` | トレイト | 時間計算ヘルパー |
| Repository.php | `src/Illuminate/Contracts/Cache/Repository.php` | インターフェース | キャッシュリポジトリ契約 |
| RedisStore.php | `src/Illuminate/Cache/RedisStore.php` | ソース | Redisキャッシュストア |
| PhpRedisConnection.php | `src/Illuminate/Redis/Connections/PhpRedisConnection.php` | ソース | PhpRedis接続クラス |
| ThrottleRequests.php | `src/Illuminate/Routing/Middleware/ThrottleRequests.php` | ソース | レート制限ミドルウェア |
