RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

Webシステムにおける HTTPサーバ機能をどう用意するか?という問題に対して先人達の葛藤の歴史

こんにちは羽山です。

現代の Webシステム界隈は昔よりもはるかに洗練され、初心者からでも簡単に開発方法を学び作れる時代になっています。その反面で例えば Python なら WSGI や gunicorn、Waitress、uWSGI などが何のために存在しているのかが分かりにくいと思ったことはありませんか?Ruby の Rack、unicorn、puma だったり FastCGI など、いずれも Webシステムの構成要素として重要な一方で役割を理解しにくいのは事実です。

そこで今回は Webシステムが現代の形にたどり着くまでの先人達の葛藤の歴史を解説します。歴史を知ればこれらの仕様やプロダクトが何の役になっているかが分かるはずです。

前提

動的な Webサイト(=Webシステム)を作りたいニーズはインターネット黎明期からありますが、ブラウザからのアクセスを適切に処理するには HTTPサーバの機能が必要です。しかし昔のプログラム言語はその機能を標準ライブラリに持たないのが一般的でした。

もちろんTCP通信を扱う機能はありますが、bind, listen して、接続に対して accept しつつプーリングしたワーカースレッドで処理という HTTPサーバ機能を自力で実装するのは大変です。
Webシステムを作りたいだけなのに、まずは HTTPサーバの開発が必要になり、本体よりもそちらのウエイトが大きくなりますし、そもそも自力で実装した HTTPサーバはセキュリティやパフォーマンス面で問題が発生しやすい問題もあります。

そこで、先人たちはすでにある外部の HTTPサーバとプログラム言語をうまくつなげれば楽ができると考えて様々な方法を生み出してきました。

CGIは最初の一歩

まず登場したのが CGI で、次のような特徴を持ちます。

HTTPサーバ上で動くプログラムはなんでも CGI にできる ので汎用性が高く、極端な例では Bash のシェルスクリプトを CGI として利用することも可能です。実際に弊社では開発環境を起動・終了する社内向けのインフラ管理システムなどで Bash CGI を活用しています。
CGI はインターネット黎明期の需要を十分満たすものでしたが、 アクセスのたびに新規プロセスを起動するオーバーヘッドが大きい のと、 複数のアクセスにまたがってメモリ空間を共有することができない ため、DB接続をプーリングするなど、高度な要件を満たすことができない問題がありました。

HTTPサーバのモジュール方式

HTTPサーバ自体にプログラム言語のランタイムを含めてしまえば常時起動が可能 という考えで生み出されたのが HTTPサーバのモジュール方式です。
この方式の元祖は古く、Apache に mod_include モジュールをロードすると利用できる SSI (Server Side Includes) による動的コンテンツも広い意味ではこの方式と言えます。SSI は1993年に初期バージョンがリリースされた NCSA httpd でも利用できました。

PHP が流行り始めた当初はモジュール方式の利用者が大半でした。
複数のアクセスをまたいでメモリ空間を共有できるので、例えば DB接続をプーリングして次回のアクセスで再利用することも可能です。
しかし HTTPサーバのワーカープロセスと生死を共にする仕組みのため、起動や終了・再起動などが一蓮托生 となり、以下に引用する PHP開発者ラスマス氏の名言(迷言?)のようにプログラム言語のランタイムの事情で HTTPサーバに影響をおよぼすデメリットがあります。

僕はホンモノのプログラマではありませんから。ホンモノのプログラマは、「動いてるように見えるけど、メモリリークだらけじゃないか。直す必要があるかもね」なんて言うでしょ? 僕なら、10リクエストごとにApacheを再起動しますね。

FastCGI

CGI の問題を解決する別のアプローチとして FastCGI は誕生しました。
HTTPサーバとは別に FastCGIサーバを常時起動して、HTTPサーバが FastCGIサーバと通信する という方式です。ずいぶん昔に生まれた仕様ながら現代主流の方式に若干近いところまでたどり着いているのが面白いと言えます。
PHP は流行当初モジュール版が主流でしたが、FastCGI の実装である php-fpm が登場して以降は、FastCGI版が今に至るまで主流です。

FastCGI は HTTPサーバと FastCGIサーバの通信ルールを仕様化しているので HTTPサーバ側は言語に依存せず利用可能です。しかし FastCGI がプログラムを動かす部分の仕様までは定められていないため、 FastCGI の実装ごとに動作するプログラムの互換性がない 点がデメリットです。

FastCGI はこの後に登場する Java Servlet と同じ系統の考え方なので重要な存在ですが、登場してしばらくは魅力的な実装が不在だったためか普及度はイマイチで、本格的に使われ始めるのは PHP5.3.3 に同梱される php-fpm の登場を待つ必要がありました。

プログラム自体に HTTPサーバ機能を内包した画期的な Java Servlet の登場

Java Servlet の登場は現代に続く礎を築いた重要なターニングポイントです。
それ以前に流行っていたのは前項までの解説の通り外部の HTTPサーバからプログラムを呼び出してもらう方式でしたが、 Java Servlet は自らが HTTPサーバとして自律的に動作 します。

個々の開発者が HTTPサーバ相当の機能を自力で実装する必要はなくライブラリとして汎用的に使える ようにした点で画期的でした。
この説明からもピンとくる方もいらっしゃると思いますが、現代はプログラム自体が HTTPサーバ機能を持って自律的に動作するのが当たり前ですが、その先駆者が Servlet であると言えます。

しかし先駆者ならではの苦悩があり、Servlet は今から振り返るとスマートなものではなく、Servlet 仕様を実装した Servlet コンテナは「ライブラリ」よりも「ミドルウェア」に近い存在 でした。
Servlet 仕様に従った Java プログラムを特定のディレクトリにデプロイしてから Servlet コンテナを起動すると、Servlet コンテナ経由でその Java プログラムが動作して HTTPプロトコルでのやりとりができます。

問題はその先で、シンプルな HTTPサーバに徹すれば良かったのに Servlet 仕様は Webアプリケーションフレームワークの領域までカバーする ものでした。

例えば Servlet仕様にはセッション機能が含まれているため Servletコンテナは勝手に JSESSIONID という Cookie を発行してセッション機能を用意します。しかし標準のセッション機能は絵に描いた餅で複数のアプリケーションサーバでロードバランスする環境ではサーバ間のセッションが同期されず、実際にはほとんど使い物になりませんでした。(※一部のつよつよ Servletコンテナにはセッションの同期機能があった)
また複数の分離された Java プログラムを動かす前提で設計されたためコンテキストパスや階層構造のクラスローダなど複雑な仕様でしたが、私の観測範囲ではプロダクション環境で単一の Tomcat 上に複数の Javaプログラムを動かすリスクを取る人は少数派でした。

このように Servlet 自体が Webアプリケーションフレームワークなので、 Java における Webアプリケーションフレームワークは Servlet フレームワークの上にさらにかぶせるフレームワーク という位置づけとなってしまった点も不格好な上に理解を難しくします。

プログラム言語に HTTPサーバ機能を内蔵する時代

Node.js, Python, Golang, Ruby などモダンな言語ではたいてい HTTPサーバ機能を標準ライブラリに内包しているので簡単なコードで HTTPサーバを起動できます。Java でも Embedded Tomcat の登場でそれら言語と同列に並べる状態になってはいますが Servlet という呪いにうっすら縛られているのはもはや仕方でしょう。

しかし標準ライブラリに含まれる HTTPサーバでプロダクション環境に耐えられる機能を簡単に実装できるかというと残念ながらそうではありません。その詳細は次の項で解説します。

WSGI, ASGI, Rack などのインターフェース仕様

セキュアかつ高パフォーマンスが必要とされる プロダクション環境で運用可能な HTTPサーバを標準ライブラリだけで作るのは難しい です。
例えば Python で利用される gunicorn は複数のアクセスを並列処理するために子プロセスを生成して効率的に処理しますが、そのレベルの HTTPサーバを自力で実装するには多くの工数を必要とします。

そこで HTTPサーバ機能を担当するライブラリとプログラムのやりとりをルール化 したものが Python の WSGI, ASGI、Ruby の Rack などです。
Python の場合は WSGI に定められたルールでプログラムを書くか、WSGI に従った Webアプリケーションフレームワークを選択すれば、gunicorn などの WSGIに対応した高品質な HTTPサーバ上で自分のプログラムを動かすことができます。

一方で本記事を執筆している現時点では Node.js に WSGI や Rack に相当する標準のインターフェース仕様は存在せず、結果として Webアプリケーションフレームワークが HTTPサーバ機能も用意しています。
Node.js は標準ライブラリが充実していて高品質な HTTPサーバ機能を提供しやすくはありますが、例えば並列処理への対応がフレームワークごとにまちまちで品質に差があります。これは Webアプリケーションフレームワークへの参入障壁となったり、ユーザー数の少ない Webアプリケーションフレームワークでは HTTPサーバ機能の品質が担保されにくい問題につながります。

まとめ

今回は Webシステムを動かすために必要な HTTPサーバ機能という縁の下の力持ちをどうやって用意するかという点に焦点を当てて歴史の話をしました。
ちなみに本記事では取り上げませんでしたが、実際にプロダクション環境で Webシステムとして構成する場合は HTTPサーバ機能のさらに前段にリバースプロキシとしての nginx, Apache や WAF(Webアプリケーションファイアウォール)、そしてロードバランサーなどと組み合わせて利用するのが一般的です。

実はこの内容は最初社内 Slack でチラ裏的に書いて流したのですが、有用そうだったので肉付け・推敲して改めてこの場で投稿させてもらいました。

「愚者は経験に学び、賢者は歴史に学ぶ」という言葉がありますが、こうやって順を追って先人の考えをトレースすると gunicorn などのありがたみがより分かるのではないでしょうか。
Webシステムを構成する各要素の知識を高めるのはエンジニアとしての基礎力を高めてくれるのでオススメです!

さて、ラクーンホールディングスではエンジニアを大募集中です!
興味を持っていただいた方は是非、お話ししましょう!

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc