プロジェクト概要 - ha1t/php-gm-server GitHub Wiki

プロジェクト概要

PHP GM Serverは、PHP言語によるGemini Protocolサーバーの実装プロジェクトです。本ページではプロジェクトの全体像、目的、技術スタック、主要な機能について説明します。

プロジェクトの目的と位置付け

プロジェクト目的

PHP GM Serverは以下の2つの目的で構築されています:

  1. PHP勉強会での発表教材 - 非同期I/O、イベントドリブンアーキテクチャ、プロトコル実装などのPHP開発技法を学習・実演するための実践的な教材
  2. 個人サイト公開基盤 - Gemini Protocol対応のシンプルで安全なサーバーとして、個人サイトやドキュメント公開基盤として運用

対応範囲

  • Geminiプロトコル仕様対応 - RFC 1953 Gemini Protocol の基本的な実装
  • TLS必須化 - セキュアな通信を標準に
  • 静的ファイル配信 - Gemini形式(.gmi)、テキスト、HTML、画像などの多様なファイルタイプをサポート
  • 自動証明書生成 - OpenSSLを用いた自己署名証明書の自動生成・管理

技術スタック

言語・フレームワーク:
  - PHP: 8.1以上
  - ReactPHP: 1.5以上(イベントループ、Socket)
  
依存パッケージ:
  - react/socket: 1.16以上 - TCP/TLSサーバー実装
  - react/event-loop: 1.5以上 - 非同期イベント処理
  
開発ツール:
  - PHPUnit: 10.5以上 - テスティングフレームワーク
  
システム要件:
  - OpenSSL拡張 - TLS/SSL通信、証明書生成

Sources: php-gm-server/composer.json:1-23, php-gm-server/README.md:1-10

プロジェクト構成

php-gm-server/
├── bin/
│   └── server.php              # エントリーポイント、設定初期化
├── src/
│   ├── GeminiServer.php        # メインサーバークラス
│   ├── RequestParser.php       # Geminiリクエストパース
│   ├── ResponseBuilder.php     # Geminiレスポンス構築
│   ├── StaticFileHandler.php   # ファイル配信
│   └── CertificateGenerator.php # TLS証明書生成
├── tests/
│   ├── GeminiServerTest.php
│   ├── RequestParserTest.php
│   ├── ResponseBuilderTest.php
│   ├── StaticFileHandlerTest.php
│   └── CertificateGeneratorTest.php
├── content/                    # ドキュメントルート(Geminiコンテンツ配置先)
├── certs/                      # TLS証明書保存ディレクトリ
├── composer.json               # 依存パッケージ定義
├── phpunit.xml                 # テスト設定
└── README.md                   # 基本的な使用方法

Sources: php-gm-server/bin/server.php:1-24, php-gm-server/composer.json:1-23

アーキテクチャ概要

システム全体フロー

php-gm-serverは、ReactPHPのイベントループを基盤とした非同期・イベント駆動型アーキテクチャを採用しています。

graph TD
    A["クライアント接続"] --> B["TLS/SSL確立"]
    B --> C["Geminiリクエスト受信"]
    C --> D["リクエストパース<br/>RequestParser"]
    D --> E{"パース成功?"}
    E -->|No| F["エラーレスポンス<br/>59 Bad Request"]
    E -->|Yes| G["ファイル処理<br/>StaticFileHandler"]
    G --> H{"ファイル存在?"}
    H -->|No| I["エラーレスポンス<br/>51 Not Found"]
    H -->|Yes| J["レスポンス構築<br/>ResponseBuilder"]
    J --> K["クライアントへ送信"]
    K --> L["接続終了"]
    F --> L
    I --> L
Loading

イベント駆動設計

GeminiServerクラスはReactPHPのイベントループを通じて、接続・データ受信・エラーを非同期で処理します:

$server->on('connection', function (ConnectionInterface $conn) {
    $conn->on('data', function (string $data) {
        // リクエスト処理
    });
    $conn->on('error', function (\Throwable $e) {
        // エラー処理
    });
});

このモデルにより、複数クライアントからの同時接続をブロッキングなしで効率的に処理できます。

Sources: php-gm-server/src/GeminiServer.php:31-90

主要なコンポーネント

1. GeminiServer(メインサーバー)

TcpServerとSecureServerを組み合わせた、Geminiプロトコル対応のTLS必須サーバーです。クライアント接続、リクエスト受信、レスポンス送信の全体フローをオーケストレーションします。

主な責務:

  • TCP 1965番ポートでリッスン(デフォルト)
  • TLS 1.2以上でのセキュアな通信確立
  • クライアントからのバイナリデータをバッファに蓄積
  • リクエスト完全性の検証(\r\nの検出)
  • リクエスト長制限(1024バイト)の強制

バッファ管理フロー:

sequenceDiagram
    participant Client as クライアント
    participant Server as GeminiServer
    participant Parser as RequestParser
    
    Client->>Server: TLS接続
    Client->>Server: Geminiリクエスト送信
    Server->>Server: バッファへデータ蓄積
    Server->>Server: \r\n検出チェック
    alt \r\n未検出
        Server->>Server: 次のデータ受信待機
    else 完全なリクエスト
        Server->>Parser: parse(buffer)
        Parser-->>Server: パース結果
        Server->>Server: ファイルハンドラへ処理依頼
    end
Loading

Sources: php-gm-server/src/GeminiServer.php:12-91

2. RequestParser(リクエスト解析)

Geminiプロトコルの仕様に基づいてリクエストを解析するコンポーネントです。

Geminiリクエスト仕様:

  • フォーマット:gemini://host/path\r\n
  • 最大長:1024バイト(MAX_REQUEST_LENGTH)
  • スキーム:gemini のみ受け入れ
  • ホスト名:必須

パース処理:

$line = explode("\r\n", $raw, 2)[0];
$parts = parse_url($line);
// スキーム、ホスト、パスを抽出・検証
return ['host' => $host, 'path' => $path];

パース失敗時(スキーム不正、ホスト名欠落、長度超過など)は null を返却し、サーバーは59 Bad Requestを送信します。

Sources: php-gm-server/src/RequestParser.php:1-48

3. ResponseBuilder(レスポンス生成)

Geminiプロトコル形式のレスポンスを生成するユーティリティクラスです。

Geminiレスポンス形式:

STATUS SPACE META\r\n
BODY

サポート対象ステータスコード:

コード 説明 メソッド 使用場面
20 成功 success(mime, body) ファイル配信成功
51 見つからない notFound() ファイル不在
59 不正リクエスト badRequest(reason) リクエストパース失敗、リクエスト長超過

実装例:

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

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

Sources: php-gm-server/src/ResponseBuilder.php:1-23

4. StaticFileHandler(ファイル配信)

ドキュメントルートからのファイル解決、パストラバーサル防止、MIMEタイプ自動判定を実装します。

主な機能:

  1. ファイルパス解決 - realpath()による正規化
  2. パストラバーサル防止 - ドキュメントルート内のみアクセス許可
  3. ディレクトリハンドリング - index.gmi を自動返却
  4. MIMEタイプ判定 - 拡張子から自動判定

サポートMIMEタイプ:

'gmi' => 'text/gemini',
'txt' => 'text/plain',
'html' => 'text/html',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'pdf' => 'application/pdf',
// その他20種類以上

パストラバーサル防止ロジック:

$realPath = realpath($filePath);
if ($realPath === false) return null;

// ドキュメントルート内チェック
if ($realPath !== $this->docRoot && 
    !str_starts_with($realPath, $this->docRoot . DIRECTORY_SEPARATOR)) {
    return null;
}

Sources: php-gm-server/src/StaticFileHandler.php:1-69

5. CertificateGenerator(TLS証明書生成)

OpenSSL拡張を使用した自己署名証明書の自動生成と管理を行います。

証明書仕様:

  • 秘密鍵:RSA 2048ビット
  • 署名方式:自己署名(CA署名なし)
  • 有効期間:365日
  • フォーマット:PEM形式
  • ファイル格納:certs/server.pem(秘密鍵と証明書が統合)

生成フロー:

  1. 既存証明書をチェック(存在すれば再利用)
  2. RSA 2048ビット秘密鍵を生成
  3. 証明書署名要求(CSR)を作成
  4. CSRに秘密鍵で署名して証明書を生成
  5. PEM形式で秘密鍵と証明書を統合して保存

セキュリティ考慮:

  • ファイルパーミッション:0600(所有者のみ読み書き)
  • 自己署名であるため、クライアント側でTOFU(Trust On First Use)モデルに基づいて信頼を確立

Sources: php-gm-server/src/CertificateGenerator.php:1-51

設定・環境変数

サーバーの動作は以下の環境変数で制御されます。全ての環境変数はオプションで、デフォルト値が定義されています。

環境変数 デフォルト値 説明 設定例
GEMINI_HOST 0.0.0.0 バインドアドレス 127.0.0.1
GEMINI_PORT 1965 リッスンポート 1966
GEMINI_DOC_ROOT content/ ドキュメントルート /var/www/gemini
GEMINI_CERT_DIR certs/ 証明書保存ディレクトリ /etc/gemini/certs
GEMINI_HOSTNAME localhost 証明書のCommon Name example.com

設定例:

GEMINI_HOSTNAME=example.com GEMINI_PORT=1965 php bin/server.php
GEMINI_DOC_ROOT=/home/user/gemini-content php bin/server.php

Sources: php-gm-server/bin/server.php:11-15, php-gm-server/README.md:26-40

リクエスト処理フロー(詳細)

クライアント接続からレスポンス送信までの完全なフロー:

graph TD
    A["TLS接続確立"] --> B["バッファ初期化<br/>buffer = ''"]
    B --> C["データ受信イベント"]
    C --> D["buffer += data"]
    D --> E{"\\r\\n検出?"}
    E -->|No| F{"buffer > 1024B?"}
    F -->|Yes| G["59 Request too long<br/>接続終了"]
    F -->|No| C
    E -->|Yes| H["RequestParser::parse"]
    H --> I{"parse結果?"}
    I -->|null| J["59 Invalid request<br/>接続終了"]
    I -->|array| K["StaticFileHandler::handle"]
    K --> L{"ファイル存在?"}
    L -->|null| M["51 Not found<br/>接続終了"]
    L -->|array| N["ResponseBuilder::success<br/>mime + body"]
    N --> O["レスポンス送信"]
    O --> P["接続終了"]
    J --> P
    M --> P
    G --> P
Loading

フロー詳細:

  1. 接続確立 - クライアントがTLSハンドシェイクを完了
  2. バッファ蓄積 - 受信したデータを保持するバッファを初期化
  3. リクエスト完全性検証 - \r\nが到達するまで受信待機
  4. 長度チェック - リクエスト超過時は即座に59エラー送信
  5. パース処理 - RequestParserでhost・pathを抽出
  6. ファイル処理 - StaticFileHandlerでファイル解決
  7. レスポンス生成 - ステータスコードとMIMEタイプを含むレスポンス構築
  8. 送信と終了 - クライアントへ送信後、接続を終了

Sources: php-gm-server/src/GeminiServer.php:44-83

実行方法

インストール

composer install

サーバー起動

php bin/server.php

デフォルト設定(0.0.0.0:1965、ドキュメントルート:content/)で起動します。初回起動時に自動的にTLS証明書がcerts/server.pemに生成されます。

テスト実行

vendor/bin/phpunit

5つのコンポーネント各々に対応したテストクラスが実行されます。

Sources: php-gm-server/README.md:11-54

セキュリティ設計

パストラバーサル攻撃の防止

StaticFileHandlerは以下の多層防御を実装:

  1. realpath()による正規化 - シンボリックリンク解決を含む絶対パス化
  2. ドキュメントルート内チェック - パス文字列によるプレフィックス確認
  3. ファイル存在確認 - 不正なパスはnullを返却

リクエスト長制限

GeminiプロトコルのMAX_REQUEST_LENGTH(1024バイト)を強制:

  • バッファが1024バイトを超えた場合、即座に59エラーを返却
  • メモリ枯渇攻撃やバッファオーバーフロー攻撃を防止

Sources: php-gm-server/src/StaticFileHandler.php:43-50, php-gm-server/src/GeminiServer.php:54-59

TLS必須化

  • すべての通信をTLS 1.2以上で暗号化
  • 自己署名証明書により、HTTPS等の商用認証局に依存しない運用が可能
  • クライアント側ではTOFU(Trust On First Use)モデルに基づいて初回接続時に証明書を信頼

Sources: php-gm-server/src/GeminiServer.php:36-40

プロジェクト目的と対応範囲

本プロジェクトは以下に対応し、それ以外はスコープ外としています:

対応範囲 ✓

  • Gemini Protocol の基本的な実装 - リクエストパース、ステータスコード(20, 51, 59)対応
  • 静的ファイル配信 - 20種類以上のMIMEタイプサポート、index.gmi自動解決
  • TLS通信 - 必須化による安全な通信
  • 自動証明書生成・管理 - 開発環境での簡便な運用
  • ユニットテスト - 各コンポーネントの機能検証

スコープ外 ✗

  • Geminiプロトコルの全機能 - CGI、リダイレクト(30x)、入力プロンプト(10x)など
  • リバースプロキシ機能
  • アクセスログの詳細な分析機能
  • マルチスレッド/プロセス対応 - ReactPHPのシングルスレッド非同期モデルで対応

Sources: php-gm-server/README.md:1-3, php-gm-server/composer.json:1-11

Related Pages

⚠️ **GitHub.com Fallback** ⚠️