PEP 3333 を読み解く
PEP 3333 は、Python 3 における WSGI(Web Server Gateway Interface)の公式仕様である。元々は PEP 333 として Python 2 向けに策定され、Python 3 での文字列・バイト列の扱いの変更に対応するために PEP 3333 として更新された。この仕様書を読み解くことで、WSGI の設計思想と技術的な詳細を深く理解できる。
WSGI の設計目標
PEP 3333 の冒頭で、WSGI の目的が明確に述べられている。Web フレームワークと Web サーバーの間に標準的なインターフェースを定義し、両者を自由に組み合わせられるようにすることだ。
PEP 3333 によると、この仕様は Web サーバーと Web アプリケーション / フレームワーク間の標準インターフェースを提案し、Python Web アプリケーションの移植性を促進することを目的としている。
WSGI 以前は、フレームワークごとにサーバーとの接続方法が異なり、特定のサーバーでしか動かないフレームワークも多かった。WSGI により、この状況が劇的に改善された。
アプリケーション側の仕様
WSGI アプリケーションは、2 つの引数を受け取る呼び出し可能オブジェクト(callable)である。
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-Type', 'text/plain')]
start_response(status, response_headers)
return [b'Hello, World!']PEP 3333 では、アプリケーションの要件を以下のように定めている。
関数、メソッド、__call__ を持つクラスのインスタンスなど、() で呼び出せるものすべてが該当する。
第 1 引数 environ は CGI 環境変数を含む辞書。第 2 引数 start_response はレスポンスヘッダーを設定するコールバック関数。
戻り値はイテラブルでなければならず、各要素はバイト列(bytes)である。文字列(str)を返すのは仕様違反だ。
environ 辞書の必須変数
PEP 3333 は、environ 辞書に含まれるべき変数を規定している。CGI 仕様(RFC 3875)由来の変数と、WSGI 固有の変数がある。
CGI 変数
| 変数名 | 説明 |
|---|---|
| REQUEST_METHOD | HTTP メソッド(GET, POST など) |
| SCRIPT_NAME | アプリケーションのルートパス |
| PATH_INFO | リクエストされたパス |
SCRIPT_NAME と PATH_INFO の関係は重要だ。SCRIPT_NAME はアプリケーションがマウントされているパスを表し、PATH_INFO はそれ以降のパスを表す。両者を連結すると、リクエストの完全なパスになる。
# 例: http://example.com/myapp/users/123 へのリクエスト
# アプリケーションが /myapp にマウントされている場合
environ['SCRIPT_NAME'] = '/myapp'
environ['PATH_INFO'] = '/users/123'WSGI 変数
| 変数名 | 説明 |
|---|---|
| wsgi.version | WSGI バージョン(1, 0)のタプル |
| wsgi.url_scheme | URL スキーム(http または https) |
| wsgi.input | リクエストボディを読むためのファイルライクオブジェクト |
wsgi.input は PEP 3333 で詳細に規定されている。read(size) と readline() メソッドをサポートする必要があり、EOF に達した場合は空のバイト列を返す。
start_response の仕様
start_response 関数は、ステータス文字列、レスポンスヘッダーのリスト、およびオプションの例外情報を受け取る。
def start_response(status, response_headers, exc_info=None):
# status: "200 OK" のような文字列
# response_headers: [('Content-Type', 'text/html'), ...] のようなリスト
# exc_info: sys.exc_info() の戻り値(例外処理時のみ)
...
return write # 非推奨の write 関数を返すステータス文字列
ステータス文字列は「3 桁のステータスコード + スペース + 理由フレーズ」の形式でなければならない。
# 正しい形式
status = '200 OK'
status = '404 Not Found'
status = '500 Internal Server Error'
# 間違った形式(理由フレーズなし)
status = '200' # 仕様違反レスポンスヘッダー
レスポンスヘッダーは (ヘッダー名, 値) のタプルを要素とするリストだ。ヘッダー名と値はどちらも文字列型(str)でなければならない。
response_headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', '1234'),
('X-Custom-Header', 'custom value'),
]PEP 3333 は、hop-by-hop ヘッダー(Connection, Keep-Alive など)をアプリケーションが設定することを禁止している。これらはサーバーが管理すべきものだからだ。
exc_info の使い方
start_response の第 3 引数 exc_info は、例外が発生した後にエラーレスポンスを送信するために使う。
import sys
def application(environ, start_response):
try:
# 何らかの処理
result = process_request(environ)
start_response('200 OK', [('Content-Type', 'text/plain')])
return [result]
except Exception:
exc_info = sys.exc_info()
start_response(
'500 Internal Server Error',
[('Content-Type', 'text/plain')],
exc_info
)
return [b'An error occurred']
finally:
exc_info = None # 循環参照を防ぐexc_info を渡す場合の動作は、ヘッダーがすでに送信されているかどうかで変わる。
新しいステータスとヘッダーでレスポンスを開始する。エラーレスポンスに置き換えられる。
例外を再送出する。一度送信したヘッダーは取り消せないため、エラーを上位に伝播させる。
write 関数(非推奨)
start_response は write 関数を返す。これは古い CGI スクリプトとの互換性のために存在するが、PEP 3333 は新しいアプリケーションでの使用を推奨していない。
def application(environ, start_response):
# 非推奨のパターン
write = start_response('200 OK', [('Content-Type', 'text/plain')])
write(b'Hello, ')
write(b'World!')
return [] # 空のイテラブルを返すwrite を使うとバッファリングが妨げられ、パフォーマンスに悪影響を与える。常にイテラブルでレスポンスを返すべきだ。
Python 3 での文字列処理
PEP 333(Python 2)から PEP 3333(Python 3)への主な変更点は、文字列とバイト列の扱いだ。
CGI 変数の値は「ネイティブ文字列」(Python 3 では str)だが、内容は ISO-8859-1 でエンコード可能な範囲に限られる。URL エンコードされたデータはそのまま格納される。
必ず bytes 型でなければならない。str を返すと仕様違反となり、サーバーがエラーを返すか、不正な動作をする可能性がある。
# 正しい例
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain; charset=utf-8')])
return [b'Hello, World!'] # bytes
# よくある間違い
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Hello, World!'] # str は仕様違反日本語などのマルチバイト文字を含むレスポンスは、明示的にエンコードする必要がある。
def application(environ, start_response):
body = 'こんにちは、世界!'
encoded_body = body.encode('utf-8')
start_response('200 OK', [
('Content-Type', 'text/plain; charset=utf-8'),
('Content-Length', str(len(encoded_body))),
])
return [encoded_body]ミドルウェアの仕様
PEP 3333 はミドルウェアの概念も規定している。ミドルウェアは「サーバーから見るとアプリケーションとして振る舞い、アプリケーションから見るとサーバーとして振る舞う」コンポーネントだ。
class Middleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# environ を加工できる(サーバーとして振る舞う)
environ['myapp.processed'] = True
# アプリケーションを呼び出す
return self.app(environ, start_response)この設計により、認証、ロギング、セッション管理などの機能を、アプリケーションコードを変更せずに追加できる。
close() メソッド
レスポンスイテラブルが close() メソッドを持っている場合、サーバーはレスポンス送信後にこのメソッドを呼び出さなければならない。これはリソースの解放に使う。
class FileResponse:
def __init__(self, filepath):
self.file = open(filepath, 'rb')
def __iter__(self):
return iter(lambda: self.file.read(8192), b'')
def close(self):
self.file.close()
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'application/octet-stream')])
return FileResponse('/path/to/file')close() を適切に実装することで、ファイルハンドルやデータベース接続などのリソースリークを防げる。
まとめ
PEP 3333 は、一見シンプルだが細部まで考え抜かれた仕様である。
environ で環境情報を受け取る
start_response でステータスとヘッダーを設定
バイト列のイテラブルでボディを返す
この基本構造を理解すれば、どんな WSGI 準拠のフレームワークやサーバーでも、その動作を予測できる。仕様書を直接読むことで、ドキュメントやチュートリアルでは触れられない細かいルールや設計意図を把握できるようになる。











