中学社会667106 views
小学社会308636 views
いろは2986023 views
数学講師2852771 views
高校物理158224 views
りんご192546 views
ヒストリア284143 views
MathPython491378 views
高校倫理1433119 views
LaTeX957300 views
Help
Tools

English

environ と start_response を理解する

WSGI アプリケーションが受け取る environstart_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_METHODPATH_INFO は必須変数であり、常に存在する。一方、QUERY_STRINGCONTENT_TYPE はリクエストによっては存在しないため、get() メソッドでデフォルト値を指定して取得するのが安全だ。

HTTP ヘッダーの参照

HTTP ヘッダーは HTTP_ プレフィックスを付けた大文字の変数名で格納される。ハイフンはアンダースコアに変換される。

HTTPヘッダーenviron キー
HostHTTP_HOST
User-AgentHTTP_USER_AGENT
AcceptHTTP_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-TypeContent-Length だけは例外で、HTTP_ プレフィックスなしの CONTENT_TYPECONTENT_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/False

wsgi.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.inputread()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_responsewrite 関数を返す。これは古い 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 url

SCRIPT_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')]

このエンドポイントにアクセスすれば、サーバーがどのような情報を渡しているかを確認できる。

environ の要点

CGI 由来の変数でリクエストの基本情報を取得し、HTTP_ プレフィックス付きの変数で HTTP ヘッダーを参照する。wsgi. プレフィックスの変数は WSGI 固有の機能を提供する。

start_response の要点

ステータス文字列とヘッダーのリストを渡してレスポンスを開始する。write() 関数は返されるが使用せず、イテラブルでボディを返すのが正しい使い方だ。

environstart_response のインターフェースは、一見シンプルだがすべての HTTP 通信を表現できる柔軟性を持っている。このインターフェースを理解すれば、どんな WSGI 準拠のサーバーやフレームワークでも、その動作を予測できるようになる。