Webサーバの動く仕組みを理解する
さて、ここで少し趣向を変えて、Webサーバの動く仕組みについて簡単に見ていきましょう。Webサーバの基本的な動作は、リクエストを受けてレスポンスを返すという単純なものです。Webサーバ自体のプログラムもそれほど難しくはありません。
これまで開発に使っていたCGIHTTPServerというWebサーバは、すべてPythonで書かれています。このWebサーバのソースコードを見れば、Webサーバの動く仕組みを理解できるはずです。
CGIHTTPServerは、大きく分けて2つのクラスから作られています。1つは、ネットワークの通信を担当するHTTPServerというクラスで、このクラスはBaseHTTPServerというモジュールに定義されています。もう1つは、リクエストを受け取り実際に処理を行うためのクラスです。CGIHTTPServerでは、CGIを実行するために必要な部分のみがCGIHTTPRequestHandlerというクラスに定義されています。このクラスはSimpleHTTPRequestHandlerというクラスを継承していて、静的なファイルを配信する一般的なWebサーバに求め られる機能はこのクラスに定義されています。SimpleHTTPRequestHandlerはBaseHTTPRequestHandlerという抽象クラスを継承しています。
CGIHTTPRequestHandlerやSimpleHTTPRequestHandlerといったクラスは、俗にハンドラと呼ばれています。リクエストを処理(ハンドリング)するための方法がハンドラとなるクラスの中に抽象化されています。必要に応じてクラスを拡張することで、リクエストをいろいろな方法で処理できます。
ファイルをレスポンスとして送信する
ハンドラクラスでは、リクエストに応答するためのハンドラメソッドが定義されています。たとえば、SimpleHTTPRequestHandlerにはGETリクエストを処理するためのdo_GET()メソッドが定義されています。send_head()というメソッドを呼び出し、戻り値としてファイルオブジェクトを受け取ってレスポンスとして返す、というとても簡潔なメソッドとなっています。self.wfileというオブジェクトが、リクエストとして返すファイルオブジェクトとして扱われているわけです。
do_GET()メソッドの実装(SimpleHTTPServer.py)
:::python
def do_GET(self):
"""Serve a GET request."""
f = self.send_head()
if f:
self.copyfile(f, self.wfile)
f.close()
さて、send_head()メソッドではどのような処理を実行しているのでしょうか。send_head()メソッドは、ドキュメンテーション文字列を取り除くと30行弱の短いコードでできています。
send_head()メソッドの実装(SimpleHTTPServer.py)
:::python
def send_head(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
if ctype.startswith('text/'): # (1)
mode = 'r'
else:
mode = 'rb'
try:
f = open(path, mode)
except IOError:
self.send_error(404, "File not found")

self.send_response(200) # (2)
self.send_header("Content-Length", str(fs[6]))
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified",
self.date_time_string(fs.st_mtime))
self.end_headers()
return f
このメソッドでは、まず最初にURLをファイル上のパスに変換しています。その後、もしパスがフォルダやディレクトリだったら、インデックスページを表示するか、あるいはディレクトリにあるファイル一覧を表示する、という場合分けをしています。
その後、ファイルの拡張子などからファイル種類を判別しています。レスポンスの種類を判別するためにヘッダに記載する情報を、ここで調べているわけです(1)。
その後には、ファイルを読み込むために開くコードが続いています。ファイルが開けなかったらエラーのステータスを返します。ファイルが開けたら、正常な動作であることを示す200という番号をステータスとして返します。このステータスはステータス行と呼ばれる文字列に変換してWebブラウザなどに送信します。
その後は、ヘッダ行を複数返しています(2)。send_header()はステータス行とヘッダを送るためのメソッドなので、本文は送信しません。do_GET()メソッドがsend_header()で開いたファイルを戻り値として受け取り、ファイルの内容をレスポンス本文として返しています。
このように、ネットワーク通信以外のWebサーバの処理は簡単に記述できるのです。
Webサーバ内でプログラムを起動する
GIHTTPRequestHandlerには、do_POST()というPOSTリクエストを処理するためのメソッドが定義されています。このメソッドで、ファイルとして置かれたプログラムを起動し、レスポンスとして返しています。do_POST()メソッドが、これまで使っていたWebサーバでプログラムを起動していた本体ということになります。
do_POST()メソッドはdo_GET()メソッドと同様に、10行に満たないとても短いプログラムです。実際にプログラムを起動しているのはrun_cgi()という200行ほどのメソッドです。ファイルを読み込むだけのsend_header()メソッドに比べてプログラムの行数が長くなっているのにはいくつか理由があります。CGIプログラムにいろいろな情報を受け渡すために多くの環境変数を設定していることが1つの理由です。たとえば、Webブラウザなどから送られてくるクエリを取り出し、環境変数に渡す必要があります。たとえば、run_cgi()メソッドの冒頭では、URLを?で分割してクエリ文字列を取り出しています。
run_cgi()メソッドの一部(CGIHTTPServer.py)
:::python
def run_cgi(self):
"""Execute a CGI script."""
dir, rest = self.cgi_info
i = rest.rfind('?')
if i >= 0:
rest, query = rest[:i], rest[i+1:]
else:
query = ''
クエリはURL上に乗って送られてくる場合もありますし、リクエスト本文に記載されている場合もあります。いろいろな可能性を考慮して、コードを書く必要があるわけです。
また、Webサーバ内でプログラムを起動し、標準出力から結果を取り出すためにサブプロセスを呼び出しています。プロセス管理はWindowsやLinuxなどOSによって処理の方法が異なります。いろいろな環境でWebサーバを動かすために、プログラム内部で場合分けをしているためにプログラムが長くなっています。
Webサーバの基本的な働き
さて、このようにしてみると、Webアプリケーションはとても単純な仕組みで動いていることが分かります。
URLを解釈する
リクエストとして返すファイルなどを特定するために、URLをディレクトリ構造として分割して目的のファイルを見つけ出します。
ステータス行を送る
これから送ろうとするレスポンスがどのようなものかを文字列で簡潔に示すためにステータス行と呼ばれる文字列を送信します。
ヘッダを送る
レスポンスの内容について、付加的な情報をWebブラウザなどに伝えるために、ヘッダ行を送ります。
レスポンス本文を送る
Webブラウザなどに対してレスポンスの本文を送ります。
一般的なWebサーバは、URLの一部をフォルダやファイルの構造(パス)として解釈して、見つけたファイルをそのままレスポンスとして送り出します。URLの一部をパスとして解釈して見つけたファイルがプログラムだった場合には、プログラムを実行して結果をレスポンスとして返します。このように動作するようにコードが書いてあるからこのような処理をしているだけであって、すべてのWebサーバがこのように動くべき、ということはまったくありません。ハンドラメソッドを変更することで、まったく異なったWebサーバを作ることも当然できます。