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

English

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__ を持つクラスのインスタンスなど、() で呼び出せるものすべてが該当する。

2 つの位置引数

第 1 引数 environ は CGI 環境変数を含む辞書。第 2 引数 start_response はレスポンスヘッダーを設定するコールバック関数。

戻り値はイテラブルでなければならず、各要素はバイト列(bytes)である。文字列(str)を返すのは仕様違反だ。

environ 辞書の必須変数

PEP 3333 は、environ 辞書に含まれるべき変数を規定している。CGI 仕様(RFC 3875)由来の変数と、WSGI 固有の変数がある。

CGI 変数

変数名説明
REQUEST_METHODHTTP メソッド(GET, POST など)
SCRIPT_NAMEアプリケーションのルートパス
PATH_INFOリクエストされたパス

SCRIPT_NAMEPATH_INFO の関係は重要だ。SCRIPT_NAME はアプリケーションがマウントされているパスを表し、PATH_INFO はそれ以降のパスを表す。両者を連結すると、リクエストの完全なパスになる。

# 例: http://example.com/myapp/users/123 へのリクエスト
# アプリケーションが /myapp にマウントされている場合
environ['SCRIPT_NAME'] = '/myapp'
environ['PATH_INFO'] = '/users/123'

WSGI 変数

変数名説明
wsgi.versionWSGI バージョン(1, 0)のタプル
wsgi.url_schemeURL スキーム(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_responsewrite 関数を返す。これは古い 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)への主な変更点は、文字列とバイト列の扱いだ。

environ の値

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 準拠のフレームワークやサーバーでも、その動作を予測できる。仕様書を直接読むことで、ドキュメントやチュートリアルでは触れられない細かいルールや設計意図を把握できるようになる。