メインコンテンツまでスキップ

CGI から Servlet へ - Web動的処理の進化

Servletを学ぶとき、「なぜこういう設計なのか」が分かると理解が深まります。Servletが生まれる前、Webで動的なページを作るにはCGIという技術が使われていました。CGIの問題を解決するためにServletが生まれた、という歴史を知ると「なるほど」と腑に落ちます。


静的Webの時代

最初のWebサーバー

┌─────────────────────────────────────────────────────────────┐
│ 静的Webの仕組み(1990年代初頭) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Webサーバーは「ファイルを返すだけ」だった │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ブラウザ Webサーバー │ │
│ │ │ │ │ │
│ │ │ ── GET /index.html ──────→ │ │ │
│ │ │ │ ファイル読み込み│ │
│ │ │ │ /var/www/ │ │
│ │ │ │ index.html │ │
│ │ │ ←── HTML ───────────────── │ │ │
│ │ │ │ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ シンプルだが、全員に同じ内容しか返せない │
│ │
└─────────────────────────────────────────────────────────────┘

動的コンテンツの需要

┌─────────────────────────────────────────────────────────────┐
│ 動的コンテンツが必要になった │
├─────────────────────────────────────────────────────────────┤
│ │
│ やりたいこと: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・ユーザーごとに違う内容を表示したい │ │
│ │ ・フォームの入力を処理したい │ │
│ │ ・データベースから情報を取得して表示したい │ │
│ │ ・現在時刻を表示したい │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 静的ファイルでは不可能 │
│ → 「プログラムを実行して結果を返す」仕組みが必要 │
│ │
└─────────────────────────────────────────────────────────────┘

CGI の登場

CGI とは

CGI(Common Gateway Interface) は、Webサーバーが外部プログラムを呼び出すための標準仕様です(1993年頃〜)。

┌─────────────────────────────────────────────────────────────┐
│ CGI の仕組み │
├─────────────────────────────────────────────────────────────┤
│ │
│ 「リクエストが来たら、プログラムを起動して結果を返す」 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ブラウザ Webサーバー CGIプログラム │ │
│ │ │ │ │ │ │
│ │ │ ── GET ────────→│ │ │ │
│ │ │ /cgi-bin/ │ │ │ │
│ │ │ hello.pl │ │ │ │
│ │ │ │ ── fork+exec ───→│ │ │
│ │ │ │ (新プロセス) │ │ │
│ │ │ │ │ 処理 │ │
│ │ │ │ ←── stdout ─────│ │ │
│ │ │ │ (HTML出力) │ 終了 │ │
│ │ │ ←── HTML ───────│ │ │ │
│ │ │ │ │ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ポイント: │
│ ・リクエストごとに新しいプロセスを起動 │
│ ・環境変数でリクエスト情報を渡す │
│ ・標準出力(stdout)がレスポンスになる │
│ ・どの言語でも書ける(Perl, C, Python, シェル...) │
│ │
└─────────────────────────────────────────────────────────────┘

CGI の実装例(イメージ)

┌─────────────────────────────────────────────────────────────┐
│ CGIプログラムの例(Perl) │
├─────────────────────────────────────────────────────────────┤
│ │
│ #!/usr/bin/perl │
│ # hello.pl │
│ │
│ # 環境変数からリクエスト情報を取得 │
│ $method = $ENV{'REQUEST_METHOD'}; │
│ $query = $ENV{'QUERY_STRING'}; │
│ │
│ # HTTPヘッダーを出力(必須) │
│ print "Content-Type: text/html\n\n"; │
│ │
│ # HTMLを出力 │
│ print "<html><body>"; │
│ print "<h1>Hello, CGI!</h1>"; │
│ print "<p>Method: $method</p>"; │
│ print "<p>Query: $query</p>"; │
│ print "</body></html>"; │
│ │
│ # プログラム終了 → プロセス終了 │
│ │
└─────────────────────────────────────────────────────────────┘

CGI のメリット

┌─────────────────────────────────────────────────────────────┐
│ CGI のメリット │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ シンプル │
│ ・「プログラムを実行して標準出力を返す」だけ │
│ ・特別なライブラリ不要 │
│ │
│ ✅ 言語非依存 │
│ ・実行可能なら何でもOK(Perl, C, Python, シェル) │
│ ・当時はPerlが人気だった │
│ │
│ ✅ プロセス分離 │
│ ・CGIプログラムがクラッシュしてもWebサーバーは無事 │
│ ・セキュリティ的に分離 │
│ │
│ 当時は画期的だった: │
│ 「Webで動的なページが作れる!」 │
│ │
└─────────────────────────────────────────────────────────────┘

CGI の問題

致命的なパフォーマンス問題

┌─────────────────────────────────────────────────────────────┐
│ CGI の問題: 1リクエスト = 1プロセス │
├─────────────────────────────────────────────────────────────┤
│ │
│ リクエストが来るたびにプロセスを起動する │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ リクエスト1 → fork → プロセス1 → 処理 → 終了 │ │
│ │ リクエスト2 → fork → プロセス2 → 処理 → 終了 │ │
│ │ リクエスト3 → fork → プロセス3 → 処理 → 終了 │ │
│ │ リクエスト4 → fork → プロセス4 → 処理 → 終了 │ │
│ │ ... │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ プロセス起動のコスト: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・OSがプロセスを作成(メモリ確保、初期化) │ │
│ │ ・プログラムをロード │ │
│ │ ・インタプリタを起動(Perl等) │ │
│ │ ・ライブラリを読み込み │ │
│ │ ・DB接続を確立 │ │
│ │ ↓ │ │
│ │ 処理(本来やりたいこと) │ │
│ │ ↓ │ │
│ │ ・DB接続を切断 │ │
│ │ ・プロセスを終了 │ │
│ │ ・メモリを解放 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ → 本来の処理より起動・終了のオーバーヘッドが大きい │
│ │
└─────────────────────────────────────────────────────────────┘

スケーラビリティの限界

┌─────────────────────────────────────────────────────────────┐
│ 同時アクセスが増えると... │
├─────────────────────────────────────────────────────────────┤
│ │
│ 10人が同時アクセス: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ プロセス × 10 = なんとか動く │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 100人が同時アクセス: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ プロセス × 100 = メモリ逼迫、レスポンス低下 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 1000人が同時アクセス: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ プロセス × 1000 = サーバーダウン 💀 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ プロセスは「重い」: │
│ ・1プロセス ≒ 数MB〜数十MBのメモリ │
│ ・コンテキストスイッチのコスト │
│ ・OSのプロセス数上限 │
│ │
│ → Webが普及してアクセス数が増えると破綻 │
│ │
└─────────────────────────────────────────────────────────────┘

状態保持の問題

┌─────────────────────────────────────────────────────────────┐
│ CGI は状態を保持できない │
├─────────────────────────────────────────────────────────────┤
│ │
│ 毎回プロセスが終了するので: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ❌ DB接続を維持できない(毎回接続し直し) │ │
│ │ ❌ キャッシュを保持できない(毎回読み直し) │ │
│ │ ❌ 設定を保持できない(毎回パースし直し) │ │
│ │ ❌ セッション情報(ファイルやDBに保存が必要) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 例: DB接続のオーバーヘッド │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CGI: │ │
│ │ リクエスト → DB接続 → クエリ → 切断 → 終了 │ │
│ │ リクエスト → DB接続 → クエリ → 切断 → 終了 │ │
│ │ リクエスト → DB接続 → クエリ → 切断 → 終了 │ │
│ │ ↑ │ │
│ │ 毎回接続(遅い) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

FastCGI - 中間の解決策

FastCGI とは

CGIの問題を軽減するため、FastCGI(1996年〜)が登場しました。

┌─────────────────────────────────────────────────────────────┐
│ FastCGI の仕組み │
├─────────────────────────────────────────────────────────────┤
│ │
│ 「プロセスを起動したままにして使い回す」 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ CGI: │ │
│ │ リクエスト → 起動 → 処理 → 終了 │ │
│ │ リクエスト → 起動 → 処理 → 終了 │ │
│ │ リクエスト → 起動 → 処理 → 終了 │ │
│ │ │ │
│ │ FastCGI: │ │
│ │ 起動(常駐) │ │
│ │ ↓ │ │
│ │ リクエスト → 処理 │ │
│ │ リクエスト → 処理 │ │
│ │ リクエスト → 処理 │ │
│ │ ↓ │ │
│ │ 終了(サーバー停止時) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 改善点: │
│ ・プロセス起動コストがなくなる │
│ ・DB接続を保持できる │
│ ・キャッシュを保持できる │
│ │
│ でも: │
│ ・プロセス間通信のオーバーヘッド │
│ ・プロセス数の管理が必要 │
│ ・言語ごとに対応が必要 │
│ │
└─────────────────────────────────────────────────────────────┘

Servlet の登場

Servlet の解決策

Java Servlet(1997年〜)は、CGIの問題を根本から解決しました。

┌─────────────────────────────────────────────────────────────┐
│ Servlet の革新 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 「1リクエスト = 1スレッド」モデル │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ CGI: 1リクエスト = 1プロセス(重い) │ │
│ │ Servlet: 1リクエスト = 1スレッド(軽い) │ │
│ │ │ │
│ │ プロセス vs スレッド: │ │
│ │ ┌───────────────┬───────────────┐ │ │
│ │ │ プロセス │ スレッド │ │ │
│ │ ├───────────────┼───────────────┤ │ │
│ │ │ 独立したメモリ │ メモリ共有 │ │ │
│ │ │ 起動: 重い │ 起動: 軽い │ │ │
│ │ │ 数MB〜 │ 数KB〜 │ │ │
│ │ │ 切替: 遅い │ 切替: 速い │ │ │
│ │ └───────────────┴───────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 同じサーバーで: │
│ ・CGI: 100同時リクエストで限界 │
│ ・Servlet: 1000同時リクエストも可能 │
│ │
└─────────────────────────────────────────────────────────────┘

シングルインスタンス + マルチスレッド

┌─────────────────────────────────────────────────────────────┐
│ Servlet のモデル │
├─────────────────────────────────────────────────────────────┤
│ │
│ Servletインスタンスは1つ、スレッドが複数 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ JVM(1プロセス) │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ Servlet(1インスタンス) │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ init() → 1回だけ実行 │ │ │ │
│ │ │ │ ・DB接続プール初期化 │ │ │ │
│ │ │ │ ・設定読み込み │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ │ ↓ │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ service() → リクエストごとに実行 │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ スレッド1 → service() │ │ │ │
│ │ │ │ スレッド2 → service() │ │ │ │
│ │ │ │ スレッド3 → service() │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ メリット: │
│ ・初期化は1回だけ(起動時) │
│ ・DB接続プールを共有できる │
│ ・キャッシュを共有できる │
│ ・設定を保持できる │
│ │
└─────────────────────────────────────────────────────────────┘

CGI vs Servlet 比較

┌─────────────────────────────────────────────────────────────┐
│ CGI vs Servlet 比較 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┬──────────────┬──────────────┐ │
│ │ │ CGI │ Servlet │ │
│ ├────────────────┼──────────────┼──────────────┤ │
│ │ 実行単位 │ プロセス │ スレッド │ │
│ │ 起動コスト │ 大きい │ 小さい │ │
│ │ メモリ効率 │ 悪い │ 良い │ │
│ │ 状態保持 │ できない │ できる │ │
│ │ DB接続プール │ 不可能 │ 可能 │ │
│ │ 同時接続数 │ 〜100程度 │ 〜1000以上 │ │
│ │ 言語 │ なんでも │ Java │ │
│ └────────────────┴──────────────┴──────────────┘ │
│ │
│ トレードオフ: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CGI: シンプル、言語自由 → パフォーマンス問題 │ │
│ │ Servlet: 高性能、状態保持可 → Java限定 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

歴史年表

┌─────────────────────────────────────────────────────────────┐
│ Web動的処理の歴史 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1991 World Wide Web 誕生(静的HTMLのみ) │
│ │ │
│ 1993 CGI 登場 │
│ │ └── 動的Webページが可能に │
│ │ └── Perl CGI が流行 │
│ │ │
│ 1995 Java 登場 │
│ │ │
│ 1996 FastCGI 登場 │
│ │ └── CGIの性能問題を軽減 │
│ │ │
│ 1997 Java Servlet 1.0 │
│ │ └── 1リクエスト1スレッドモデル │
│ │ └── CGIの問題を根本解決 │
│ │ │
│ 1999 Servlet 2.2 / JSP 1.1 │
│ │ └── J2EEの一部に │
│ │ └── Tomcat 3.x │
│ │ │
│ 2000年代 │
│ │ └── Struts, Spring などフレームワーク登場 │
│ │ └── Servlet上に高レベルな抽象化 │
│ │ │
│ 2014 Spring Boot 登場 │
│ │ └── 組み込みTomcat │
│ │ └── Servletを意識せず開発可能に │
│ │ │
│ 現在 Servlet は「基盤」として健在 │
│ └── Spring Boot, Jakarta EE の土台 │
│ │
└─────────────────────────────────────────────────────────────┘

他言語での同様の進化

┌─────────────────────────────────────────────────────────────┐
│ 他言語でも同じ問題を解決 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CGIの問題は言語を問わず発生した │
│ 各言語で「Servlet的なもの」が生まれた │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PHP: mod_php │ │
│ │ ・ApacheにPHPインタプリタを組み込み │ │
│ │ ・プロセス起動なしでPHP実行 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Python: WSGI (2003) │ │
│ │ ・Webサーバーとアプリの標準インターフェース │ │
│ │ ・Gunicorn, uWSGI がServletコンテナ的な役割 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Ruby: Rack (2007) │ │
│ │ ・WSGIのRuby版 │ │
│ │ ・Puma, Unicorn がServletコンテナ的な役割 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Node.js (2009) │ │
│ │ ・最初から常駐プロセス前提 │ │
│ │ ・CGI的な発想がそもそもない │ │
│ │ ・イベントループモデル │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Servletは「Java版の解決策」だが、思想は共通 │
│ │
└─────────────────────────────────────────────────────────────┘

まとめ

なぜServletはこういう設計なのか

┌─────────────────────────────────────────────────────────────┐
│ 歴史から学ぶServletの設計意図 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【なぜスレッドモデル?】 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CGIの1プロセス/リクエストは重すぎた │ │
│ │ → スレッドなら軽い │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 【なぜシングルインスタンス?】 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 初期化コストを1回で済ませたい │ │
│ │ リソース(DB接続等)を共有したい │ │
│ │ → 1つのインスタンスを複数スレッドで共有 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 【なぜスレッドセーフが重要?】 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ シングルインスタンス + マルチスレッド │ │
│ │ → インスタンス変数は複数スレッドで共有される │ │
│ │ → スレッドセーフに注意が必要 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 【なぜinit()/destroy()がある?】 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CGIでは毎回起動/終了していた │ │
│ │ Servletは常駐するので、ライフサイクルフックが必要 │ │
│ │ → init()で初期化、destroy()でクリーンアップ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

歴史を知る価値

知識得られること
CGIの問題Servletの設計意図が分かる
プロセス vs スレッドパフォーマンス特性が分かる
他言語の解決策共通パターンが見える

次のステップ