ResponseBuilderコンポーネント - ha1t/php-gm-server GitHub Wiki

ResponseBuilderコンポーネント

ResponseBuilderは、Gemini Protocol に準拠したレスポンスを構築するための静的ヘルパークラスです。このコンポーネントは、ファイルハンドラーの処理結果をGeminiプロトコル形式(STATUS SPACE META\r\n BODY)に変換し、クライアントへの送信用レスポンスを生成します。成功レスポンス(ステータス20)、見つからないエラー(ステータス51)、不正リクエストエラー(ステータス59)の3種類のレスポンスメソッドを提供し、サーバー全体のレスポンス処理を一元管理します。

概要

役割と位置付け

ResponseBuilderクラスは、GeminiServerコンポーネントの内部で使用され、StaticFileHandlerコンポーネントの処理結果をGemini Protocol形式のレスポンスに変換する責務を持ちます。プロトコル仕様に準拠したレスポンス文字列を生成することで、異なるクライアント実装間での互換性を確保します。

システム内での位置付け

sequenceDiagram
    participant Client as Gemini Client
    participant Server as GeminiServer
    participant Parser as RequestParser
    participant Handler as StaticFileHandler
    participant Builder as ResponseBuilder

    Client->>Server: TLS Connection + Request
    Server->>Parser: parse(buffer)
    Parser-->>Server: {host, path}
    Server->>Handler: handle(path)
    Handler-->>Server: {mime, body} or null
    
    alt File Found
        Server->>Builder: success(mime, body)
        Builder-->>Server: "20 mime\r\nBody"
    else File Not Found
        Server->>Builder: notFound()
        Builder-->>Server: "51 Not found\r\n"
    else Invalid Request
        Server->>Builder: badRequest(reason)
        Builder-->>Server: "59 reason\r\n"
    end
    
    Server->>Client: Response (Gemini Format)
    Server->>Client: End Connection

Gemini Protocol レスポンス形式

仕様概要

Gemini Protocol のレスポンスは、以下の構造を持つ単純なテキスト形式です:

STATUS SPACE META\r\n
[BODY]
  • STATUS: 1〜3桁の整数(ステータスコード)
  • SPACE: 単一のスペース文字(U+0020)
  • META: 最大1024バイトの任意テキスト(ステータスに応じた情報)
  • CRLF: キャリッジリターンとラインフィード(\r\n)
  • BODY: オプショナルなレスポンスボディ(バイナリまたはテキスト)

ステータスコード仕様

ResponseBuilderが生成する3つのステータスコードと対応する処理フロー:

ステータス 名称 用途 META BODY 出典
20 Success(成功) ファイル取得成功 MIMEタイプ ファイル内容 src/ResponseBuilder.php:9-11
51 Not found(見つからない) ファイルが存在しない "Not found" 無し src/ResponseBuilder.php:14-16
59 Bad request(不正リクエスト) リクエスト形式が無効 カスタム理由文 無し src/ResponseBuilder.php:19-21

ResponseBuilder クラス実装

クラス構造

ResponseBuilderクラスは、名前空間 GeminiServer に属する静的メソッドのみで構成されるユーティリティクラスです。インスタンス化は想定されず、すべてのメソッドは static として定義されています。

出典: src/ResponseBuilder.php:7-23

メソッド仕様

success(string $mime, string $body): string

正常に取得したファイルの内容をレスポンスとして構築します。

public static function success(string $mime, string $body): string
{
    return "20 {$mime}\r\n{$body}";
}

パラメータ:

  • $mime (string): レスポンスボディのMIMEタイプ(例:text/geminitext/plainimage/png
  • $body (string): ファイルの内容またはレスポンスボディ

戻り値: Gemini形式のレスポンス文字列(ステータス20)

:

ResponseBuilder::success('text/gemini', 'Hello Gemini!')
// 結果: "20 text/gemini\r\nHello Gemini!"

出典: src/ResponseBuilder.php:9-11

notFound(): string

要求されたリソースが見つからない場合のレスポンスを構築します。

public static function notFound(): string
{
    return "51 Not found\r\n";
}

パラメータ: 無し

戻り値: ステータス51のレスポンス文字列(BОDYなし)

:

ResponseBuilder::notFound()
// 結果: "51 Not found\r\n"

用途: リクエストされたファイルがドキュメントルート内に存在しない場合、または存在してもディレクトリで対応するindex.gmiが無い場合に使用されます。

出典: src/ResponseBuilder.php:14-16

badRequest(string $reason = 'Bad request'): string

クライアントリクエストが不正な形式の場合のレスポンスを構築します。

public static function badRequest(string $reason = 'Bad request'): string
{
    return "59 {$reason}\r\n";
}

パラメータ:

  • $reason (string, optional): エラー理由を説明する文字列。デフォルト値は 'Bad request'

戻り値: ステータス59のレスポンス文字列(BОDYなし)

:

ResponseBuilder::badRequest('Invalid request')
// 結果: "59 Invalid request\r\n"

ResponseBuilder::badRequest('Request too long')
// 結果: "59 Request too long\r\n"

ResponseBuilder::badRequest()
// 結果: "59 Bad request\r\n"

用途: リクエスト形式が無効(スキームが gemini でない、ホスト名が不正、リクエスト長が1024バイト超過)な場合に使用されます。

出典: src/ResponseBuilder.php:19-21

使用コンテキスト

GeminiServerでの使用フロー

GeminiServerクラス内の接続ハンドラーで、3つのシナリオに応じてResponseBuilderメソッドが呼び出されます:

  1. ファイル取得成功時: ResponseBuilder::success() を呼び出し

    $result = $this->fileHandler->handle($request['path']);
    $conn->write(ResponseBuilder::success($result['mime'], $result['body']));
    
  2. ファイル見つからない時: ResponseBuilder::notFound() を呼び出し

    if ($result === null) {
        $conn->write(ResponseBuilder::notFound());
    }
    
  3. 不正リクエスト時: ResponseBuilder::badRequest() を呼び出し

    if (strlen($buffer) > 1024) {
        $conn->write(ResponseBuilder::badRequest('Request too long'));
    }
    

出典: src/GeminiServer.php:44-82

エラーシナリオの詳細

flowchart TD
    A["リクエスト受信"] --> B{"リクエスト形式検証"}
    B -->|CRLF未検出 & 長さ ≤ 1024| C["バッファに蓄積"]
    B -->|CRLF未検出 & 長さ > 1024| D["badRequest: Request too long"]
    B -->|CRLF検出| E["RequestParser::parse"]
    C --> E
    E -->|パース失敗| F["badRequest: Invalid request"]
    E -->|パース成功| G["StaticFileHandler::handle"]
    G -->|ファイル取得成功| H["success: mime + body"]
    G -->|ファイル不在| I["notFound"]
    D --> J["レスポンス送信 & 接続終了"]
    F --> J
    H --> J
    I --> J

テスト仕様

ResponseBuilderクラスのテストは、PHPUnit フレームワークを使用して実装されています。全メソッドのレスポンス形式が Gemini Protocol 仕様に準拠することを検証します。

テストケース

テスト名 検証対象 アサーション 出典
testSuccessResponse success() レスポンス形式が 20 {mime}\r\n{body} tests/ResponseBuilderTest.php:12-15
testNotFoundResponse notFound() レスポンスが 51 Not found\r\n tests/ResponseBuilderTest.php:18-21
testBadRequestResponse badRequest() レスポンスが 59 {reason}\r\n tests/ResponseBuilderTest.php:24-27
testSuccessWithEmptyBody success() (空ボディ) 空ボディでも形式維持 tests/ResponseBuilderTest.php:30-33

テスト実行例

vendor/bin/phpunit tests/ResponseBuilderTest.php

出典: tests/ResponseBuilderTest.php

プロトコル準拠性

Gemini Protocol 仕様との合致

ResponseBuilderが生成するレスポンスは、Geminiプロトコル仕様実装に記載されたレスポンス形式仕様に完全に準拠しています:

  • 形式: STATUS SPACE META\r\nBODY 構造を厳密に守る
  • 改行コード: CRLF(\r\n)を正確に使用
  • ステータスコード: サーバーが対応する20、51、59のみを生成
  • エラーレスポンス: BОDYなしの簡潔なMETA情報を返す

MIMEタイプ対応

StaticFileHandlerが提供するMIMEタイプ一覧(ファイル配信システム詳細参照):

  • text/gemini (.gmi, .gemini)
  • text/plain (.txt)
  • text/html (.html)
  • text/css (.css)
  • text/javascript (.js)
  • application/json (.json)
  • image/png (.png)
  • image/jpeg (.jpg, .jpeg)
  • image/gif (.gif)
  • image/svg+xml (.svg)
  • application/pdf (.pdf)
  • application/octet-stream (既定)

出典: src/StaticFileHandler.php:11-25

アーキテクチャ的位置付け

コンポーネント間の責任分離

classDiagram
    class RequestParser {
        +parse(string): ?array
    }
    
    class StaticFileHandler {
        -docRoot: string
        -MIME_MAP: array
        +handle(string): ?array
    }
    
    class ResponseBuilder {
        +success(string, string): string
        +notFound(): string
        +badRequest(string): string
    }
    
    class GeminiServer {
        -host: string
        -port: int
        -fileHandler: StaticFileHandler
        -certPath: string
        +run(): void
    }
    
    GeminiServer --> RequestParser
    GeminiServer --> StaticFileHandler
    GeminiServer --> ResponseBuilder

各コンポーネントの責責任:

  • RequestParser: リクエスト文字列の解析のみ(パース層)
  • StaticFileHandler: ファイル解決とMIMEタイプ判定(ビジネスロジック層)
  • ResponseBuilder: Gemini形式レスポンス生成(フォーマット層)
  • GeminiServer: コンポーネント間の制御フロー管理(オーケストレーション層)

設計原則

ResponseBuilderの設計は以下の原則に従っています:

  1. 単一責任の原則: レスポンス形式生成に特化
  2. ステートレス: インスタンス状態を持たない静的メソッド
  3. イミュータビリティ: 入力パラメータを変更しない
  4. 簡潔性: 必要最小限の実装で仕様を実現

Related Pages