environ と start_response を理解する
WSGI アプリケーションが受け取る environ と start_response は、HTTP リクエストとレスポンスを抽象化した WSGI の核心部分である。この 2 つのインターフェースを深く理解することで、Web アプリケーションの動作原理が明確になる。
environ の構造
environ は Python の辞書であり、CGI 環境変数に加えて WSGI 固有の変数が含まれている。リクエストに関するあらゆる情報がこの辞書に格納される。
CGI 由来の変数
HTTP リクエストの基本情報は CGI 仕様に由来する変数として提供される。
def dump_environ(environ, start_response):
# リクエストの基本情報
method = environ['REQUEST_METHOD'] # GET, POST, PUT など
path = environ['PATH_INFO'] # /users/123 など
query = environ.get('QUERY_STRING', '') # key=value&foo=bar
protocol = environ['SERVER_PROTOCOL'] # HTTP/1.1
# サーバー情報
server_name = environ['SERVER_NAME'] # localhost
server_port = environ['SERVER_PORT'] # 8000
# コンテンツ情報(POST リクエストなど)
content_type = environ.get('CONTENT_TYPE', '')
content_length = environ.get('CONTENT_LENGTH', '')
info = f"""
Method: {method}
Path: {path}
Query: {query}
Protocol: {protocol}
Server: {server_name}:{server_port}
Content-Type: {content_type}
Content-Length: {content_length}
"""
start_response('200 OK', [('Content-Type', 'text/plain')])
return [info.encode('utf-8')]REQUEST_METHOD と PATH_INFO は必須変数であり、常に存在する。一方、QUERY_STRING や CONTENT_TYPE はリクエストによっては存在しないため、get() メソッドでデフォルト値を指定して取得するのが安全だ。
HTTP ヘッダーの参照
HTTP ヘッダーは HTTP_ プレフィックスを付けた大文字の変数名で格納される。ハイフンはアンダースコアに変換される。
| HTTPヘッダー | environ キー |
|---|---|
| Host | HTTP_HOST |
| User-Agent | HTTP_USER_AGENT |
| Accept | HTTP_ACCEPT |
def show_headers(environ, start_response):
headers_info = []
for key, value in environ.items():
if key.startswith('HTTP_'):
# HTTP_USER_AGENT → User-Agent に変換
header_name = key[5:].replace('_', '-').title()
headers_info.append(f"{header_name}: {value}")
body = '\n'.join(headers_info)
start_response('200 OK', [('Content-Type', 'text/plain')])
return [body.encode('utf-8')]Content-Type と Content-Length だけは例外で、HTTP_ プレフィックスなしの CONTENT_TYPE と CONTENT_LENGTH として格納される。これは CGI 仕様との互換性のためである。
WSGI 固有の変数
WSGI 仕様で追加された変数には wsgi. プレフィックスが付く。
# WSGI バージョン(タプル)
wsgi_version = environ['wsgi.version'] # (1, 0)
# URL スキーム
scheme = environ['wsgi.url_scheme'] # 'http' または 'https'
# リクエストボディを読むためのファイルライクオブジェクト
input_stream = environ['wsgi.input']
# エラー出力用のファイルライクオブジェクト
error_stream = environ['wsgi.errors']
# マルチスレッド/マルチプロセスの情報
multithread = environ['wsgi.multithread'] # True/False
multiprocess = environ['wsgi.multiprocess'] # True/False
run_once = environ['wsgi.run_once'] # True/Falsewsgi.input はリクエストボディを読み取るための入り口だ。POST データやファイルアップロードの内容はここから取得する。
def read_post_body(environ, start_response):
try:
content_length = int(environ.get('CONTENT_LENGTH', 0))
except ValueError:
content_length = 0
if content_length > 0:
body = environ['wsgi.input'].read(content_length)
else:
body = b''
start_response('200 OK', [('Content-Type', 'application/octet-stream')])
return [body]wsgi.input の read() は CONTENT_LENGTH で指定されたバイト数だけ読むべきだ。引数なしで read() を呼ぶと、サーバーによってはブロックしてしまう可能性がある。
start_response の役割
start_response はサーバーから渡されるコールバック関数で、レスポンスのステータスとヘッダーを設定するために使う。
基本的な呼び出し
def application(environ, start_response):
status = '200 OK'
response_headers = [
('Content-Type', 'text/html; charset=utf-8'),
('X-Custom-Header', 'some-value'),
]
start_response(status, response_headers)
return [b'<h1>Hello</h1>']status は「ステータスコード + 理由フレーズ」の文字列である。response_headers は (ヘッダー名, 値) のタプルを要素とするリストだ。
start_response の戻り値
start_response は write 関数を返す。これは古い CGI スクリプトとの互換性のために存在するが、現代の WSGI アプリケーションでは使用すべきでない。
def legacy_style(environ, start_response):
status = '200 OK'
headers = [('Content-Type', 'text/plain')]
# write 関数を受け取る(非推奨)
write = start_response(status, headers)
# write() でレスポンスを書く(非推奨)
write(b'Hello ')
write(b'World')
# 空のイテラブルを返す
return []write() はバッファリングを妨げ、パフォーマンスに悪影響を与える。常にイテラブルでレスポンスを返す方式を使うべきだ。
exc_info 引数
start_response は第 3 引数として exc_info を受け取ることができる。これは例外が発生した後にエラーレスポンスを送信するためのものだ。
import sys
def application(environ, start_response):
try:
# 何らかの処理
result = do_something()
start_response('200 OK', [('Content-Type', 'text/plain')])
return [result.encode('utf-8')]
except Exception:
# 例外情報を取得
exc_info = sys.exc_info()
# エラーレスポンスを送信(exc_info を渡す)
start_response(
'500 Internal Server Error',
[('Content-Type', 'text/plain')],
exc_info
)
return [b'An error occurred']exc_info を渡すと、すでにヘッダーが送信されていた場合に例外を再送出する。ヘッダー送信前であれば、新しいステータスとヘッダーで上書きする。
environ の拡張
ミドルウェアやフレームワークは、environ に独自のキーを追加して情報を伝達することがある。
class SessionMiddleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# セッション情報を environ に追加
session_id = self._get_session_id(environ)
environ['myapp.session'] = self._load_session(session_id)
return self.app(environ, start_response)独自キーには、衝突を避けるためにプレフィックスを付けるのが慣例だ。werkzeug. や paste. のように、ライブラリ名やアプリケーション名を使う。
完全なリクエスト URL の構築
environ の情報を組み合わせて、リクエストの完全な URL を構築できる。
def get_full_url(environ):
scheme = environ['wsgi.url_scheme']
host = environ.get('HTTP_HOST', '')
if not host:
host = environ['SERVER_NAME']
port = environ['SERVER_PORT']
if (scheme == 'http' and port != '80') or \
(scheme == 'https' and port != '443'):
host = f"{host}:{port}"
path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
query = environ.get('QUERY_STRING', '')
url = f"{scheme}://{host}{path}"
if query:
url = f"{url}?{query}"
return urlSCRIPT_NAME はアプリケーションがマウントされているパスを表す。ルートにマウントされていれば空文字列だ。PATH_INFO と組み合わせることで、正しいパスを構築できる。
environ を使ったデバッグ
開発時には、environ の内容をすべてダンプするエンドポイントを用意しておくと便利だ。
def debug_environ(environ, start_response):
lines = []
for key in sorted(environ.keys()):
value = environ[key]
# バイナリや複雑なオブジェクトは文字列化
if isinstance(value, bytes):
value = f"<bytes: {len(value)} bytes>"
elif hasattr(value, 'read'):
value = f"<file-like object>"
lines.append(f"{key}: {value}")
body = '\n'.join(lines)
start_response('200 OK', [('Content-Type', 'text/plain; charset=utf-8')])
return [body.encode('utf-8')]このエンドポイントにアクセスすれば、サーバーがどのような情報を渡しているかを確認できる。
CGI 由来の変数でリクエストの基本情報を取得し、HTTP_ プレフィックス付きの変数で HTTP ヘッダーを参照する。wsgi. プレフィックスの変数は WSGI 固有の機能を提供する。
ステータス文字列とヘッダーのリストを渡してレスポンスを開始する。write() 関数は返されるが使用せず、イテラブルでボディを返すのが正しい使い方だ。
environ と start_response のインターフェースは、一見シンプルだがすべての HTTP 通信を表現できる柔軟性を持っている。このインターフェースを理解すれば、どんな WSGI 準拠のサーバーやフレームワークでも、その動作を予測できるようになる。











