プロジェクト概要 - ha1t/php-gm-server GitHub Wiki
PHP GM Serverは、PHP言語によるGemini Protocolサーバーの実装プロジェクトです。本ページではプロジェクトの全体像、目的、技術スタック、主要な機能について説明します。
PHP GM Serverは以下の2つの目的で構築されています:
- PHP勉強会での発表教材 - 非同期I/O、イベントドリブンアーキテクチャ、プロトコル実装などのPHP開発技法を学習・実演するための実践的な教材
- 個人サイト公開基盤 - 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
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
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
Sources: php-gm-server/src/GeminiServer.php:12-91
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
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
ドキュメントルートからのファイル解決、パストラバーサル防止、MIMEタイプ自動判定を実装します。
主な機能:
- ファイルパス解決 - realpath()による正規化
- パストラバーサル防止 - ドキュメントルート内のみアクセス許可
-
ディレクトリハンドリング -
index.gmiを自動返却 - 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
OpenSSL拡張を使用した自己署名証明書の自動生成と管理を行います。
証明書仕様:
- 秘密鍵:RSA 2048ビット
- 署名方式:自己署名(CA署名なし)
- 有効期間:365日
- フォーマット:PEM形式
- ファイル格納:
certs/server.pem(秘密鍵と証明書が統合)
生成フロー:
- 既存証明書をチェック(存在すれば再利用)
- RSA 2048ビット秘密鍵を生成
- 証明書署名要求(CSR)を作成
- CSRに秘密鍵で署名して証明書を生成
- 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.phpSources: 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
フロー詳細:
- 接続確立 - クライアントがTLSハンドシェイクを完了
- バッファ蓄積 - 受信したデータを保持するバッファを初期化
- リクエスト完全性検証 - \r\nが到達するまで受信待機
- 長度チェック - リクエスト超過時は即座に59エラー送信
- パース処理 - RequestParserでhost・pathを抽出
- ファイル処理 - StaticFileHandlerでファイル解決
- レスポンス生成 - ステータスコードとMIMEタイプを含むレスポンス構築
- 送信と終了 - クライアントへ送信後、接続を終了
Sources: php-gm-server/src/GeminiServer.php:44-83
composer installphp bin/server.phpデフォルト設定(0.0.0.0:1965、ドキュメントルート:content/)で起動します。初回起動時に自動的にTLS証明書がcerts/server.pemに生成されます。
vendor/bin/phpunit5つのコンポーネント各々に対応したテストクラスが実行されます。
Sources: php-gm-server/README.md:11-54
StaticFileHandlerは以下の多層防御を実装:
- realpath()による正規化 - シンボリックリンク解決を含む絶対パス化
- ドキュメントルート内チェック - パス文字列によるプレフィックス確認
-
ファイル存在確認 - 不正なパスは
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 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
- アーキテクチャ設計 — システム全体のコンポーネント構成、各コンポーネント間の関係、データフロー、設計パターンを図解
- Geminiプロトコル仕様実装 — Gemini Protocol の概要、リクエスト・レスポンス形式、ステータスコード、MIMEタイプ仕様
- インストール・セットアップガイド — システム要件、Composer依存パッケージ導入、初回起動、自動証明書生成の流れ
- 環境変数設定リファレンス — GEMINI_HOST、GEMINI_PORT、GEMINI_DOC_ROOT、GEMINI_CERT_DIR、GEMINI_HOSTNAME の詳細仕様
- GeminiServerコンポーネント — GeminiServer クラスの役割、接続ハンドラーの処理フロー、バッファ管理、エラーハンドリング
- RequestParserコンポーネント — RequestParser クラスの実装、Geminiリクエストのパース処理、スキーム検証、長度チェック
- ResponseBuilderコンポーネント — ResponseBuilder クラスの実装、Geminiレスポンス形式の構築、各ステータスコードのメソッド
- StaticFileHandlerコンポーネント — ドキュメントルート内のファイル解決、ディレクトリハンドリング、MIME判定、パストラバーサル防止機構
- ファイル配信システム詳細 — ファイル配信フロー、サポートMIMEタイプ一覧、index.gmi 自動解決、デフォルトMIMEタイプ
- セキュリティ設計 — パストラバーサル防止、リクエスト長制限、TLS必須化、TOFU モデル
- CertificateGeneratorコンポーネント — OpenSSL拡張による自動生成、RSA 2048ビット秘密鍵、CSR処理、証明書有効期限
- TLS証明書管理 — 証明書の自動生成・管理、certs/ ディレクトリ構成、環境変数による制御、TOFU モデル
- エラーハンドリング戦略 — ステータスコード 51・59 の使い分け、接続エラー処理、バッファオーバーフロー時の処理
- リクエスト処理フロー — クライアント接続からレスポンス送信までの完全フロー、各段階でのエラー処理
- レスポンス構築フロー — ファイルハンドラーからの結果に基づくレスポンス生成、ステータスコードと META 情報の付与
- テスト戦略と実装 — テストスイート構成、PHPUnit 設定、各コンポーネントのユニットテスト対象範囲
- 依存パッケージと外部統合 — react/socket、react/event-loop、PSR-4 オートロード設定
- ディレクトリ構成と規約 — プロジェクトディレクトリ構成、各ディレクトリの役割、PSR-4 規約