Skip to content

03 HTTP and Web

KITO, takayuki edited this page Aug 13, 2026 · 1 revision

3. Web リクエストの処理 (HTTP & Web)

Woof では、Web アプリケーションの入力である HTTP リクエストと、出力である HTTP レスポンスを、すべて洗練されたオブジェクトとして扱います。

この章では、リクエストの受け取りからルーティング、高度なヘッダー解析、そして多彩なレスポンスの返し方までを解説します。

1. ルーティングと Controller

Woof は「純粋関数」としての見通しの良さを保つため、複雑なルーティングエンジンを内蔵していません。ルーティングは単に、 RequestEnvironment を受け取り、適切な Controller を返す 1 つの関数 (クロージャ) として定義します。

このアプローチにより、特定の URL にマッチしなかった場合 (404 Not Found) の制御なども非常に美しく、かつ明確に記述できます。

$router = function (Request $request, WebEnvironment $env): Controller {
    $path = $request->getUri()->getPath();

    if ($path === "/") {
        return new HomeController();
    }
    if ($path === "/api/users") {
        return new UserApiController();
    }

    // どの条件にもマッチしなかった場合は 404 用の Controller を返す
    return new NotFoundController();
};

$controller = $router($request, $env);
$response = $controller->handle($request, $env);

2. Request と高度な HTTP ヘッダー解析

クライアントから送信された情報は、すべて不変な Request オブジェクトにカプセル化されています。 Woof の強力な点は、複雑な HTTP ヘッダーを単なる文字列としてではなく、意味を持ったオブジェクトとしてパース (解析) できることです。

Quality Values の解析 (Accept-Language など)

ブラウザが送信する Accept-LanguageAccept などのヘッダーには、優先度を示す q 値 (Quality Values) が含まれています。 Woof はこれを自動で解釈し、優先度順にソートして扱うことができます。

use Woof\Http\QualityValues;

// "ja,en-US;q=0.7,en;q=0.3" のような文字列を解析して取得
$acceptLanguage = $request->getHeader("Accept-Language");

// ヘッダーが QualityValues としてパースされているか確認
if ($acceptLanguage instanceof QualityValues) {
    // getValue() は q値の優先度順にソートされた配列を返します
    $values = $acceptLanguage->getValue();
    
    // 最も優先度の高い言語を取得します (この例では "ja") 
    $primaryLanguage = $values[0] ?? null; 
}

HTTP 日付の解析 (If-Modified-Since など)

キャッシュ制御などで用いられる HTTP 特有の日付フォーマットも HttpDate オブジェクトとして安全に扱うことができます。

use Woof\Http\HttpDate;

$ifModifiedSince = $request->getHeader("If-Modified-Since");

// ヘッダーが HttpDate としてパースされているか確認
if ($ifModifiedSince instanceof HttpDate) {
    // HttpDate の getValue() は int 型 (Unix タイムスタンプ) を返します
    $timestamp = $ifModifiedSince->getValue();
    
    // そのまま int 同士の比較に使用したり、date() 関数に渡して書式化したりできます
    if ($timestamp < time()) {
        // ...
    }
}

3. Response と多彩なレスポンスボディ

Controller の処理結果は、最終的に Response オブジェクトとして出力されます。 レスポンスボディ (コンテンツの中身) には、用途に合わせて様々な専用クラスをセットすることができます。

  • TextBody: プレーンテキストや HTML 文字列を直接返す場合に使用します。
  • JsonBody: 配列やオブジェクトを JSON 形式で返す API などに使用します。
  • FileBody: サーバー上のファイルをブラウザで表示させたり、ダウンロードさせたりする場合に使用します。
use Woof\Web\Operator;
use Woof\Http\Response\JsonBody;
use Woof\Http\Response\FileBody;

// JSON API のレスポンス例
return (new Operator($request, $env))
    ->setBody(new JsonBody(["status" => "success", "id" => 123]))
    ->build();

// 画像ファイルをブラウザ上で表示させる例
return (new Operator($request, $env))
    ->setBody(new FileBody("/path/to/image.png", "image/png"))
    ->build();

// PDF ファイルを強制的にダウンロードさせる例
return (new Operator($request, $env))
    ->setBody(new FileBody("/path/to/document.pdf", "application/pdf"))
    ->setAttachmentFilename("report.pdf") // ここで Content-Disposition ヘッダーが自動的にセットされます
    ->build();

View の使用と「純粋な値」のルール

HTML テンプレートエンジンなどを利用して動的な画面を描画する場合は View インターフェースを実装したクラスを作成し、 OperatorsetView() メソッドに渡します。

ここで、Woof における 極めて重要な実装のゴールデンルール を再確認します。

「View クラスのコンストラクタには、純粋な値 (不変な状態) だけを渡すこと」

データベースと直接通信するような ORM クラス (例: $userModel) をそのまま View に渡してはいけません。 必ず、Controller 側で必要なデータ (ユーザー名 $userName や、表示フラグ $isAdmin など) を取り出し、それらの「純粋な値」だけを View に渡してください。

これにより、View が外部の環境から完全に切り離されるため、入力と出力が 1:1 になる美しい単体テストの設計が可能になります。

Clone this wiki locally