PEP 3333を日本語訳しました

この記事の概要

自分でwebサーバを作ってみようと思っていろいろと勉強しています。

仕事ではDjangoを使っているので、言語はやっぱりPythonです。となると、Python製のwebサーバとアプリを接続するためのWSGI規格についても知る必要があります。

PEP 3333はWSGI規格を定義した公式のドキュメントで、PEP 333の後継にあたります。言語的な訓練も兼ねてこれを翻訳してみました。

  • 訳していない箇所もありますが、規格の定義に関しては網羅してあります。
  • 書き方に一貫性がないところも多いです。

原文はこちら

↓以下が本文です↓

概要

このドキュメントは、webサーバとPython製webアプリ(またはフレームワーク)をつなぐ標準のインターフェイスを定めるものです。目標は、webアプリとさまざまなwebサーバの互換性を向上させることです。

理由と目的(PEP 333より)

Pythonのすごいところは、少し数えただけでも、Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Webといったwebアプリ用フレームワークがたくさんあることです。このような膨大な選択肢はPythonの新しいユーザにとっては悩みのタネかもしれません。なぜなら、たいていはwebフレームワークごとに使えるwebサーバが限られてくるし、その逆もしかりだからです。

それとは対象的に、Javaにはたくさんのフレームワークがありますが、「サーブレット」というAPIのおかげで、その仕様さえサポートしてれば、どんなwebフレームワークをどんなwebサーバ上で動かすことも可能です。

このようなAPIPython向けwebサーバ(これはMudusaのようにPythonで書かれていても、mod_pythonのように埋め込まれていても、CGIFastCGIのように外部からPythonを実行しても構いません)に備わり、普及すれば、フレームワークとwebサーバの選定が独立するので、ユーザは自由に適切な組み合わせが選べるし、開発者は自分の専門領域に集中できます。

以上のような理由から、このPEPは、webサーバとwebアプリ(フレームワーク)をつなぐ、シンプルで汎用的なインターフェイスを提案します。それがPython Web Server Gateway Interface (WSGI)です。

とはいえ、WSGI規格が存在するだけでは、Python製webアプリのサーバーとフレームワークをとりまく状況は変わりません。 WSGIが効果を発揮するためには、サーバーとフレームワークの開発者たちが実装を行う必要があります。

しかし、既存のサーバやフレームワークWSGIをサポートしているものは存在しないので、WSGIに対応した開発者はすぐにちょっとした称賛を受けるでしょう。 なのでWSGIかならず 簡単に実装でき、開発者の初期投資が十分低くなければいけません。

そのため、サーバーとフレームワーク両方の 実装をシンプルにすることが、WSGIの効用を高めるために絶対に必要であり、仕様策定の原則的な指標になります。

しかし注意しなければいけないのは、フレームワークの開発者にとってシンプルな実装が、webアプリの開発者にとっても便利とは限らないということです。WSGIは「簡素だけど実用的」なインターフェイスのみを提供するものです。なぜなら、レスポンスオブジェクトやクッキーのような便利機能は、既存のフレームワークがとっくに対応しているからです。繰り返しますが、WSGIの目標は、既存のサーバとアプリ(フレームワーク)の相互接続を楽にすることであり、新しいwebフレームワークを作ることではありません。

また、現行のPythonで利用できない機能を、WSGIが必要とすることもない、ということにも留意してください。だから、この規格において、新しい標準ライブラリモジュールが提案されたり要求されたりすることはありません。WSGIが必要とするのはバージョン2.2.2以上のPythonのみです。(しかしながら、将来のバージョンにおいて、標準ライブラリがこのwebサーバ向けインターフェイスをサポートするのはすばらしいことです)

既存の、あるいは将来のフレームワークやサーバが実装しやすいのに加えて、リクエストの前処理やレスポンスの後処理、その他の機能を持つWSGI依存の「ミドルウェア」も簡単に作成できるようにするべきです。こうしたコンポーネントは、サーバからアプリのように見え、アプリにとってはサーバのように振る舞います。

もしミドルウェアがシンプルかつ強力で、WSGIがサーバとフレームワーク界隈で普及していれば、まったく新しいタイプのPython製webアプリフレームワークの可能性がひらけるでしょう。つまり、疎結合な複数のWSGIミドルウェアで構成されたフレームワークです。それどころか、既存のフレームワークの開発者たちさえ、自分たちのフレームワークの機能をそのような形で提供するようになるでしょう。モノリシックなフレームワークではなく、よりライブラリに近い形です。これによりアプリ開発者は、ひとつのフレームワークの長所と短所をすべて受け入れるのではなく、「最高品質」のコンポーネントを特定の機能ごとに選ぶことができるようになります。

もちろん、これを書いている現在からすると、その日は疑いもなく遠いでしょう。 いまのところ、WSGIの短期的な目標は、どんなフレームワークをどんなサーバー上でも使えるようにすることです。

最後に、このバージョンのWSGIは、アプリをサーバ(またはゲートウェイ)と共に「デプロイ」する際の、特定の方法を規定するものではない、ということにも触れておかなければいけません。現在、これはサーバーまたはゲートウェイが実装の中で定義するべきものです。さまざまなデプロイ要件をもつWSGIサーバやフレームワークが十分な実戦経験を積んだら、また別のPEPの中で標準化されるでしょう。

規格概要

WSGIインターフェイスには2つの側面があります。「サーバ」あるいは「ゲートウェイ」側と「アプリ」あるいは「フレームワーク」側です。サーバ側は、アプリ側から提供されるcallableオブジェクトを実行します。そのオブジェクトがどのように提供されるかは、サーバ(ゲートウェイ)が決めることです。ある種のサーバ(ゲートウェイ)の場合、アプリをデプロイする人がスクリプトを書く必要があります。その中で、サーバ(ゲートウェイ)のインスタンスを作成し、アプリオブジェクトを渡します。 別のサーバ(ゲートウェイ)では、設定ファイルや他の方法で、アプリオブジェクトをインポートしたり取得したりする方法を指定するものもあります。

「純粋」なサーバ(ゲートウェイ)やアプリ(フレームワーク)に加えて、両サイドを実装する「ミドルウェア」を作ることもできます。そうしたコンポーネントは、サーバ側から見るとアプリのように、アプリ側から見るとサーバのように振る舞います。そして、APIの拡張、コンテンツの変換、ナビゲーションやその他の便利な機能を提供してくれます。

この規格書の中で、「callable」は「関数、メソッド、クラス、インスタンスで、 __call__ メソッドを持つもの」という意味で使われます。それぞれのサーバ(ゲートウェイ)やアプリが、自分たちの要件に合った方法でcallableを実装することができます。逆に、callableを実行する側は、どのようなcallableが与えられるかに依存しては いけません [must not] 。Callableはただ呼ばれる [called] だけであり、中身を知る必要はありません。

文字列型についての注意

一般的にHTTPはバイト列を扱います。つまりこの規格はほとんどバイト列についてのものということです。

とはいえ、こうしたバイト列の内容はある種のテキスト的な解釈が必要になることがよくあります。Pythonにおいては、文字列がテキストを扱う上で一番便利な方法です。

しかしPythonの多くのバージョンや実装では、文字列はバイト列ではなくUnicodeです。このため、APIの使いやすさと、HTTPにおけるバイト列とテキストの変換の正確さは、慎重にバランスさせる必要があります。特に、異なる str 型の実装を持つPython実装へのコードの移行をサポートする場合はそれが顕著です。

こうした理由から、WSGIは2つの「文字列」を定義しています。

  • 「ネイティブ」文字列(常に str 型という命名)。リクエスト/レスポンスのヘッダーやメタデータとして使用されます。

  • 「バイト列」(Python 3では bytes 、その他では str )。リクエストやレスポンスのbodyで使用されます(たとえばPOST/PUTの入力データやHTMLページなど)。

しかし、勘違いをしないように気をつけてください。たとえPythonの「内部で」 str 型がUnicodeだったとしても、ネイティブ文字列の 中身 は依然としてLatin-1エンコーディングに変換できなければいけません。(詳細はこのドキュメントで後述される「Unicode問題」の項を参照ください。)

まとめるとこういうことです。このドキュメントで「文字列」という単語があれば、それは「ネイティブ」文字列です。つまり、内部でバイト列であろうとUnicodeであろうと、 str 型のオブジェクトを指します。「バイト列」についての言及は、「Python 3なら bytesで、Python 2以下なら str」と考えてください。

そして、確かにHTTPはある意味「単なるバイト列」ですが、Pythonのデフォルトの str 型がなんであっても使用できる便利なAPIをたくさん備えています。

アプリ・フレームワーク

アプリオブジェクトは単なるcallableオブジェクトで、2つの引数が渡されます。「オブジェクト」という用語を、いわゆるオブジェクトインスタンスと誤解しないようにしましょう。関数、メソッド、クラス、インスタンス__call__ メソッドを持つものなら、すべてアプリオブジェクトとして有効です。アプリオブジェクトは2回以上呼ばれることが可能である必要があります。なぜなら、全てのサーバやゲートウェイは(CGIを除いて)そのような連続のリクエストをする可能性があるからです。

(註: 「アプリ」オブジェクトとは呼んでいますが、アプリ開発者がWSGIをweb制作APIとして使う、という意味に誤解しないようにしましょう。アプリ開発者は、すでにある高レベルのフレームワークを使ってアプリ制作を行います。WSGIフレームワークやサーバ開発者のためのツールであり、アプリ開発者を直接サポートするためのものではありません。)

アプリオブジェクトとして、2つの例を挙げます。1つは関数で、もう1つはクラスです。

HELLO_WORLD = b"Hello world!\n"

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]

class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield HELLO_WORLD

サーバ・ゲートウェイ

サーバ(ゲートウェイ)は、アプリに対するリクエストをHTTPクライアントから受け取るごとに、callableなアプリを1度実行します。説明のために、アプリオブジェクトを受け取る関数として実装された、シンプルなCGIゲートウェイを用意しました。この単純な例ではエラー処理に限界があることに注意してください。なぜなら、デフォルトではキャッチされなかった例外は sys.stderr に流れ、webサーバにロギングされるからです。

import os, sys

enc, esc = sys.getfilesystemencoding(), 'surrogateescape'

def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')

def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')

def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
    environ['wsgi.input']        = sys.stdin.buffer
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        out = sys.stdout.buffer

        if not headers_set:
                raise AssertionError("write() before start_response()")

        elif not headers_sent:
                # Before the first output, send the stored headers
                status, response_headers = headers_sent[:] = headers_set
                out.write(wsgi_to_bytes('Status: %s\r\n' % status))
                for header in response_headers:
                    out.write(wsgi_to_bytes('%s: %s\r\n' % header))
                out.write(wsgi_to_bytes('\r\n'))

        out.write(data)
        out.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]

        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.

        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

ミドルウェア: 2役を演じるコンポーネント

ひとつのオブジェクトが、アプリに対してはサーバとして振る舞いつつ、サーバに対してはアプリとして振る舞うこともあります。このような「ミドルウェアコンポーネントは以下のような機能を持つことができます。

  • 目的のURLに応じて、 environ を書き換えたあと、リクエストを別のアプリオブジェクトにルーティングする

  • 同じプロセスのなかで、複数のアプリ(フレームワーク)を並べて実行 [run side-by-side] できるようにする

  • ネットワークごしにリクエストを転送し、ロードバランシングやリモート処理を行う

  • XSLスタイルシートの適用など、コンテンツに対する後処理を行う

ミドルウェアの存在は基本的に、サーバ(ゲートウェイ)とアプリ(フレームワーク)の双方から不可視であり、特別な対応を必要としてはいけません。ミドルウェアをアプリに組み込む場合、サーバに対してはミドルウェアがまるでアプリであるかのように認識させ、アプリを実行する際にはミドルウェアがサーバであるかのように設定しておく必要があります。もちろん「アプリ」というのも、実は別のアプリを包み込むミドルウェア、つまり「ミドルウェアスタック」なのかもしれません。

大部分において、ミドルウェアWSGIにおけるサーバとアプリの両方の制限と要件に従う必要があります。しかし場合によってはミドルウェアの要件は「純粋な」サーバやアプリよりも厳しい場合があります。これについてはこの規格書のなかで後述されます。

冗談みたいな例ですが、以下のミドルウェアはJoe Stroutの piglatin.py を使って、 text/plain レスポンスを似非ラテン語に変換しています。(註: 「本当の」ミドルウェアはもっと確実な方法でcontent typeをする必要がありますし、content encodingも確認しなければいけません。また、この単純な例では単語がブロック境界 [block boundary] で分割される可能性を無視しています。)

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty bytestring, so 'transform_ok' has to be a mutable
    truth value.
    """

    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).__next__
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def __next__(self):
        if self.transform_ok:
            return piglatin(self._next())   # call must be byte-safe on Py3
        else:
            return self._next()

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status, response_headers, exc_info=None):

            # Reset ok flag, in case this is a repeat call
            del transform_ok[:]

            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name, value)
                        for name, value in response_headers
                            if name.lower() != 'content-length'
                    ]
                    break

            write = start_response(status, response_headers, exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))   # call must be byte-safe on Py3
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

規格詳細

アプリオブジェクトは2つの位置引数を受けつける必要があります。わかりやすさのために environstart_response という名前がついていますが、実際はこれと同じ名前である必要はありません。 サーバ(ゲートウェイ)はアプリオブジェクトを(キーワード引数でなく) かならず 位置引数で実行する必要があります。(たとえば、前述の result = application(environ, start_response のような呼び出し方です)

environ 引数は、CGI形式の環境変数を含んだ辞書オブジェクトです。このオブジェクトは かならず Pythonの組み込み型である辞書(サブクラスや UserDict や他の辞書を真似たものではありません)でなくてはいけませんし、アプリはその辞書を好きな方法で変更できる必要があります。この辞書にはWSGIに必須な特定の変数(後述)が入っていなくてはいけません。また、サーバ独自の拡張変数をあとで述べるような命名形式で入れることもできます。

start_response 引数はcallableであり、2つの必須位置引数と1つの任意引数をとります。わかりやすさのためにstatus , response_headers そして exc_info命名されていますが、かならずしもこうした名前である必要はありません。ただ、アプリが start_response を実行するときは かならず 位置引数でおこなう必要があります(例えば start_response(status, response_headers) のように)。

status 引数はHTTPステータスの文字列であり、 "999 Message hre" という形式です。response_headers はリストであり、中身はHTTPレスポンスヘッダーを示す (header_name, header_value) というタプルです。任意引数である ecx_info については、以降の「start_response() Callable」と「例外処理」の項で詳説されています。 これはアプリがエラーをキャッチしたときにのみ使われ、エラーメッセージをブラウザに表示するためのものです。

start_response callableは、1つの位置引数を取る write(body_data) callableを返す必要があります。この引数は、HTTPレスポンスbodyの一部として書き込まれるバイト列です。(註: write() callableは、すでにある特定のフレームワークにとって必須な出力APIをサポートするためだけに存在します。なので、避けられるのであれば、新しいアプリやフレームワークが使う必要はありません。詳細は「バッファリングとストリーミング」の項を参照ください。)

サーバから実行されるとき、アプリオブジェクトは0以上のバイト列をyieldするイテラブルを返さなければいけません。これを実現する方法はいくつも考えられます。たとえば、アプリが文字列のリストを返したり、アプリを、文字列をyieldするジェネレータ関数にするといった方法です。またアプリを、イテラブルなインスタンスを作るクラスとして実装したりすることもできます。 どのような方法で実現するにしても、アプリオブジェクトは常に、0以上の文字列をyieldするイテラブルを返す必要があります。

サーバ(ゲートウェイ)は返ってきた文字列を、 バッファに貯めずに送る必要があります。つまり、すべての文字列を1回のリクエストですべて送信しなければいけません。言言いかえると、アプリは自分自身でバッファリングを行うべき [ should ] ということです。アプリの出力方法については、以下の「バッファリングとストリーミング」の項を参照ください。

サーバ(ゲートウェイ)は、その文字列をバイナリ列としてあつかうべきです。特に、行末についてはその逆でないよう気をつけましょう。文字列がクライントむけの正しいフォーマットになっているかを保証するのは、アプリ側の責任です。サーバ(ゲートウェイ)は、HTTP用のエンコーディングを適用したり、他にもbyte-rangeの送信のような、HTTP機能を実現する処理を行うことができます [ may ] 。詳細は以下の「その他のHTTP機能」を参照してください。

len(iterable) が実行が可能な場合、その結果をサーバ側が精確なものとして信頼できる必要があります。つまり、アプリの返却したイテラブルが実行可能な __len__() メソッドを持っていた場合、 かならず 正しい値を返す必要があるということです。(この値が一般的にどのような用途に使われるかについては、「Content-lengthヘッダーのあつかいかた」の項を参照してください)

アプリの返却したイテラブルが close() メソッドを持っていた場合、サーバ(ゲートウェイ)は現在のリクエストへの対応終了時に かならず 呼び出す必要があります。これはリクエストへの対応が正常に終了しても、イテレーション中にアプリ側エラーによって中断しても、ブラウザからの接続が早期に切断されても同様です。(アプリによるリソースの解放を助けることが目的です。この規約は、PEP 342におけるジェネレータや、close() メソッドを持つその他のイテラブルを補完するものです。)

ジェネレータやその他のカスタムイテレータを返すアプリは、これらがすべて消費されること前提にするべきではありません [ should not ] 。サーバによって早期に閉じられる可能性もある [ may ] からです。

(註: アプリの start_response() の実行は、 かならず イテラブルが最初のbody文字列を生成する前に行う必要があります。そうすることでbodyを読み込む前にサーバがヘッダーを送ることができるからです。しかし、この実行はイテラブルの最初のイテレーションで行われる可能性もあります [ may ] 。なのでサーバは、イテラブルのイテレーションを開始するまでは、 start_response() がすでに呼ばれているとみなしてはいけません [ must not ] 。

最後に、サーバ(ゲートウェイ)は、アプリから返却されたイテラブルの、その他のアトリビュートを直接使ってはいけません [ must not ] 。ただし、wsgi.file_wrapper から返される「ファイルラッパー」といった、サーバ(ゲートウェイ)固有の型のインスタンスは除きます(「任意のプラットフォーム固有ファイル操作」を参照ください)。たいていの場合、これまでに示されたアトリビュートと、PEP234で定義されたイテレーションAPIのみが使用可能です。

environ 変数

environ 辞書は、Common Gateway Interface規格に定義された以下のCGI環境変数を含んでいる必要があります。以下の変数は かならず 存在している必要があります。ただし、値が空文字であり、また以下に註記もされていない場合、省略することも 可能 [ may ] です。

REQUEST_METHOD

GETPOST のようなHTTPリクエストメソッド。This cannot ever be an empty string, and so is always required.

SCRIPT_NAME

リクエストURL「パス」のなかで、アプリオブジェクトに対応する初めの部分です。これによりアプリは自分自身の仮想「ロケーション」を知ることができます。アプリケーションがサーバの「ルート」に位置する場合、空文字でも構いません [ may ] 。

PATH_INFO

リクエストURL「パス」の残りの部分です。アプリ内の仮想「ロケーション」を指定します。リクエストURLがアプリのルートであり、末尾のスラッシュがない場合は空文字でも構いません [ may ] 。

QUERY_STRING

リクエストURLのうち、 "?" の後に続く部分です。 空文字、あるいは存在しない可能性があります。

CONTENT_TYPE

HTTPリクエストにおける Content-Type の中身です。 空文字、あるいは存在しない可能性があります。

CONTENT_LENGTH

HTTPリクエストにおける Content-Length の中身です 空文字、あるいは存在しない可能性があります。

SERVER_NAME , SERVER_PORT

これらの変数を SCRIPT_NAME PATH_INFO と組み合わせることで、URLを完成させることができます。しかし、 HTTP_HOST が存在する場合、 SERVER_NAME よりもそちらを優先してURLを再構築するべきです。詳細は「URLの再構築」の項をご覧ください。SERVER_NAMESERVER_PORT は絶対に空文字にはならないため、必須項目です。

SERVER_PROTOCOL

クライアントがそのリクエストを送るために使っているプロトコルのバージョンです。 これはたいてい "HTTP/1.0" または HTTP/1.1 であり、アプリがHTTPリクエストヘッダーをどのように処理するかを決めるために使用されます。(この変数はおそらく REQUEST_PROTOCOL と呼ばれるべきでしょう。なぜならリクエストで使われているプロトコルを示すものであり、サーバからのレスポンスに使われるプロトコルと同じであるとは限らないからです。しかし、CGIとの互換性を保つために既存の名前を使う必要があります。)

HTTP_ 変数

クライアントから送られるHTTPリクエストのヘッダーに対応する変数です(これらの変数の名前は HTTP_ から始まります)。これらの変数の在・不在は、リクエスト内の対応するHTTPヘッダーの在・不在と同じです。

サーバ(ゲートウェイ)は、可能なかぎり多くのCGI変数を渡すべきです [ should ] 。さらに、SSLが有効な場合、サーバ(ゲートウェイ)は、できるだけ多くのApache SSL変数(HTTPS=onSSL_PROTOCOL など)を渡すべきです [ should ]。 しかし、先ほどリストアップされたもの以外のCGI変数を使用するアプリは、それらをサポートしないwebサーバ上でかならず動作するとは限りません。たとえば、ファイルを配信しないwebサーバは意味のある DOCUMENT_ROOTPATH_TRANSLATED を渡すことができません。

WSGI対応サーバ(ゲートウェイ)は、どのような変数を提供するのかを、定義とともにドキュメント化するべきです [ should ] 。アプリはどんな変数が必須なのかを確認し、そうした変数が提供されていない場合の対処法を決めておくべきです [ should ] 。

註:欠けている変数、たとえば認証が行われていない状態での REMOTE_USER などは、 environ 辞書に含めるべきではありません。そして、CGI定義の変数が存在する場合、かならずネイティブ文字列を使うように注意してください。どんな CGI変数であれ、 str 以外の型を使用するのはこの規格からの逸脱です。

CGIに定義された変数に加え、 environ 辞書は、OSの任意の「環境変数」を含むことができます [ may ] し、以下のようにWSGIに定義された変数を かならず 含まなければいけません。

wsgi.version

WSGIバージョン1.0を示すタプル (1, 0)

wsgi.url_scheme

アプリが実行されるURLのうち、「スキーマ」を示す部分の文字列。ふつう、これは "http""https" の適切なほうの値が入ります。

wsgi.input

HTTPリクエストbodyを読むことのできる入力ストリーム(file-likeオブジェクト)です。サーバ(ゲートウェイ)は、アプリの要求に応じて読み取ることもできますし、先に読んでおいてメモリやディスクに保存しておくこともできますし、好みに応じて他のどのような方法で入力ストリームを渡しても構いません。

wsgi.errors

エラー出力を書き込むことができる出力ストリーム(file-likeオブジェクト)です。これは、プログラム上やその他の箇所で発生したエラーを、標準化された形式で、場合によっては特定の場所にまとめて記録するためのものです。 これは「テキストモード」のストリームである必要があります。つまり、アプリ側は "\n" を改行に使い、のちにサーバ(ゲートウェイ)側で適切な形式に変換するということです。

strUnicodeである環境では、エラーストリームは任意のUnicodeを例外を投げることなく受け付けて出力する べき です。しかしストリームのエンコーディングで使用できない場合は別の文字で代用することが可能です。)

多くのサーバにとって、 wsgi.erros はメインのエラーログになるはずです。別の方法としては、 sys.stderr やログファイルを用いることもできます。サーバのドキュメントは、設定方法と記録場所について説明をする必要があります。サーバ(ゲートウェイ)は、必要であればアプリごとに別のエラー出力を提供することもできます。

wsgi.multithread

アプリオブジェクトが、同じプロセス内の別のスレッドで同時に実行される場合、この値はtrueであり、そうでなければfalseです。

wsgi.multiprocess

同じ働きをするアプリオブジェクトが、別のプロセスから実行される場合、この値はtrueであり、そうでなければfalseです。

wsgi.run_once

プロセスの生存期間のなかで、サーバ(ゲートウェイ)が、アプリを1度だけ実行することが期待される(しかし保証されない)場合、この値はtrueです。一般に、この値がtrueになるのは、CGI(またはそれに類するもの)に依拠したゲートウェイのみです。

最後に、 environ 辞書はサーバ側で定義された変数も持つことができます。これらの変数は、小文字、数字、ドット、アンダースコアのみを使用し、サーバ(ゲートウェイ)の名前でユニークにプリフィックスされているべきです。たとえば mod_python なら、 mod_python.some_variable のように変数を定義するとよいでしょう。

入力とエラーストリーム

サーバが提供する入力とエラーのストリームは、以下のメソッドを実装している必要があります。

Method Stream Notes
read()size input 1
readline() input 1, 2
readlines(hint) input 1, 3
__iter__() input
flush() errors 4
write(str) errors
writelines(seq) errors

各メソッドの意味はPython Library Referenceに記載されているとおりですが、上記の表に示された註には気をつけてください。

1

サーバはクライアントの指定した Content_Length のサイズを超えて読みこむ必要はありませんし、アプリ側がそれ以上読み込もうとした場合は、サーバ側はファイルの終端に達したとみなすべき [should] です。 アプリは CONTENT_LENGTH で指定されたデータ量を越えて読み込もうとするべきではありません [ should not ] 。

サーバは read() を引数なしで実行できるようにし、クライアントの入力ストリームの残り全てを返すようにするべき [should] です。

空の、または読み尽くされた入力ストリームを読み込もうとする場合、サーバはつねに空のバイト列を返すべき [should] です。

2

サーバは任意引数の size を取る readline() をサポートするべき [should] です。ただ、これは WSGI 1.0では省略することが可能でした。

WSGI 1.0では、実装が複雑になるわりにはあまり実用されないという理由で、size引数はサポートされていませんでした。しかし、 cgi モジュールがそれを使うようになったため、実用的なサーバーはなんにせよサポートを始める必要があります)

3

readlines() の受け付ける引数である hint は、実装する側にとっても呼び出す側にとっても任意です。アプリ側は提供してもいいですし、サーバ(ゲートウェイ)側は無視することができます。

4

errors ストリームを巻き戻すことはないので、サーバ(ゲートウェイ)はバッファリングすることなく即座に書き込み処理をおこなうことができます。 なので、この場合 flush() メソッドはno-opになります。しかし、アプリの移植性を維持するなら、出力がバッファリングされていないとか、 flush() がno-opであると決めつけることはできません。出力が確実に書き込まれていることを保証したい場合、かならず flush() を呼ぶ必要があります。(たとえば、複数プロセスが同じエラーログに書き込むさいに、データが混じってしまうことを避けたいときなど)

この規格にしたがうサーバは、上記のメソッドを かならず サポートしている必要があります。この規格にしたがうアプリは、上記以外のメソッドや、前述した input errors オブジェクトの属性以外は 絶対に 使ってはいけません。特に、これらのストリームが close() メソッドを持っていたとしても、アプリは 絶対に 閉じようとしてはいけません。

start_response() Callable

アプリオブジェクトに渡される2つ目の引数は、 start_resnpose(status, response_headers, exc_info=None) という形式のcallableです。 (他のWSGI callableと同様に、引数はキーワードでなく位置指定で渡される必要があります。)start_response callableはHTTPレスポンスを開始するために実行され、かならず write(body_data) callableを返す必要があります(後述の「バッファリングとストリーミング」の項を参照ください)。

status 引数は、 "200 OK""404 Not Found" のようなHTTPステータスの文字列です。つまり、Status-CodeとReason-Phreseが上記のような順番でならび、1つのスペースが挟まり、両端に空白や他の文字がくっついていない文字列です。 (詳しくはRFC 2616のセクション6.1.1を参照ください。)この文字列は 絶対に 制御文字を含んでいてはいけませんし、 キャリッジリターン、ラインフィード、またはその両方で終端してはいけません。

response_headers 引数は、 (header_name, header_value) というタプルのリストです。これはかならずPythonのリストである必要があります。つまり、 type(response_heanders) is ListType であり、サーバはその中身を好きなように変更することができます [ may ] 。それぞれの header_name は有効なHTTPヘッダーのフィールド名(RFC 2616, Section 4.2で定義されています)でなければいけませんし、終端にコロンや他の記号をつけてはいけません。

それぞれの header_valueどんな 制御記号も、キャリッジリターンやラインフィードも例外なく、途中であれ終端であれ 絶対に 含んでいてはいけません。 (こうした規定は、サーバ、ゲートウェイあるいはその中間でレスポンスを変更するソフトウェアが、応答ヘッダーを調べたり変更するさいの、パース作業の複雑さを軽減するためのものです。)

一般的に、正しいヘッダーがクライアントに送られることを保証するのは、サーバ(ゲートウェイ)側の役割です。アプリがHTTP(またはその他の有効な規格)に定義された必須のヘッダーを省略した場合、サーバ(ゲートウェイ)は かならず 補う必要があります。たとえば、 DateServer といったヘッダーはふつうサーバ(ゲートウェイ)から提供されるものです。

(サーバやゲートウェイを作成している人へ。HTTPヘッダーの命名は小文字と大文字を区別しません。なので、アプリから渡されたヘッダーを調べるときはそのことを肝に命じておきましょう。)

HTTP/1.1以降、アプリやミドルウェアは、「ホップバイホップ」機能やヘッダー、HTTP/1.0における同様の機能、またクライアントとwebサーバの接続の持続性に影響をあたえるヘッダーを使用することは禁止されています。これらの機能は実際のwebサーバのあつかう領域であり、サーバ(ゲートウェイ)は、アプリがこれらを送ってくる場合は致命的なエラーとみなし、例外を投げるべき [ should ] です。「ホップバイホップ」機能やヘッダーの詳細については、以下の「その他のHTTP機能」の項を参照ください。

サーバは start_response が呼ばれた際にヘッダーのエラーをチェックする べき です。これによってアプリが走っている間にエラーを投げることができます。

しかし、 start_response callableは 絶対に レスポンスヘッダーを実際に送信してはいけませんそうではなく、ひとまずはサーバ(ゲートウェイ)のために保管しておかなければならず、送信が可能になるのは、アプリが最初のイテレーションを終えて空文字以外の文字列を生成した後、あるいはアプリが最初に write() callableを実行したとき のみ です。言いかえると、実際のbodyデータが使用できるようになるまで、またはアプリの返したイテラブルを読み尽くすまで、レスポンスヘッダーは送信してはいけない、ということです。(このルールの唯一の例外は、レスポンスヘッダーのなかで、Content_Length がゼロと明示されている場合です。)

このようにレスポンスヘッダー送信を遅延させるのは、バッファリングされた非同期アプリが、最後の最後まで、当初意図していた出力をエラー出力で上書きできるようにするためです。たとえば、bodyがアプリのバッファ内で生成されている最中にエラーが発生した場合、アプリはレスポンスのステータスを"200 OK"から"500 Internal Error"に変更する必要があるでしょう。

exc_info() 引数は、もし渡されているのなら、Pythonsys.exc_info() のタプルです。この引数は、 start_response がエラーハンドラによって呼ばれているときのみ、アプリから返されます。exc_info が返され、そしてHTTPヘッダーがまだ出力されていない場合、 start_response はすでに保存されているHTTPレスポンスヘッダーを新しいものに書き換える必要があります。そうすることで、エラー発生時に、アプリが出力に関して「心変わり」することができます。

しかし、 exc_info が返され、すでにHTTPヘッダーが送信されている場合、 start_resnponseかならず 例外を投げ、 できれば [should] exc_info タプルを使って再送出するべきです。つまり以下のようにします。

raise exc_info[1].with_traceback(exc_info[2])

このコードは、アプリ側で捉えられた例外を再送出し、原則的にはアプリを中断させます。(HTTPヘッダーがすでに送信されたあとにエラーを出力するのは、アプリにとって安全ではありません。)start_responseexc_info 引数とともに呼ばれた場合、 アプリは start_response から投げられた例外を 絶対に 捕捉してはいけません。かわりに、そうした例外はサーバ(ゲートウェイ)に伝搬するにまかせしょう。詳細は以下の「例外処理」の項を参照してください。

アプリは、 exc_info 引数が与えられている場合に限り、 start_response を2回以上呼ぶことが可能 [may] です。より詳しくいえば、アプリが実行されてすでに start_response が呼ばれているにもかかわらず、 start_responseexc_info 引数なしで再度呼ぶのは致命的なエラーです。これは、start_response の最初の呼び出しがエラーを投げてしまうケースを含みます。(正しいロジックの説明は上記のCGIゲートウェイの例を参照ください。)

註: start_response を実装するサーバ、ゲートウェイミドルウェアは、関数実行の期間を越えて exc_info への参照を保持するべきではありません。これはトレースバックとそれに関わるフレームの循環参照を避けるためです。もっともシンプルなやり方は以下のようなものです。

def start_response(status, response_headers, exc_info=None):
    if exc_info:
            try:
                # do stuff w/exc_info here
            finally:
                exc_info = None    # Avoid circular ref.

前述のCGIゲートウェイのコードはこのテクニックを使った別の例です。

Content-Length ヘッダーの扱いかた

アプリが Content-Length ヘッダーを返す場合、サーバーはヘッダーが示すより多くのデータを送信する べきではありません し、すでに十分なデータが送信されている場合はレスポンスのイテレーションをやめる べきです。また、アプリがそのデータ量を越えて write() を実行しようとする場合は例外を投げるべきです。(もちろん、アプリが Content-Length に対応する 十分な データ量を返さない場合、サーバは接続を閉じてログやその他の方法でエラーを通知する べき です。)

アプリが Content-Length ヘッダーを返さない場合、サーバ(ゲートウェイ)はいくつかの方法のうち1つで対応することができます。もっとも単純なのは、レスポンスの終了とともに、クライアントとの接続を閉じることです。

しかし特定の状況において、サーバ(ゲートウェイ)は Content-Length ヘッダーを追加するか、最低でもクライアントとの接続を保ちつづける必要があります。アプリが write() callableを呼ばず、 len() が1のイテラブルを返す場合、サーバはそのイテラブルの返す最初のバイト列の長さが Content-Length であると自動的にみなすことができます。

また、サーバとクライアントの両方がHTTP/1.1の「chunked encoding」に対応している場合、サーバは write() の呼び出しごとのチャンクや、イテラブルの生成するバイト列を、chunked encodingで送信することが 可能 です。この場合、 Content-Length ヘッダーはそれぞれのチャンクごとに生成します。これにより、サーバは必要であればクライアントとの接続をキープすることができます。この場合、サーバはRFC2616を遵守する必要があることに留意してください。さもなければ、Content-Length の不在に対応する他の方法を選びましょう。

(註: アプリやミドルウェアは、出力に対して Transfer-Encoding に類するものを 絶対に 適用してはいけません。例えばチャンク化やgzip化などです。「ホップバイホップ」と同様に、こうしたエンコーディングは実際のwebサーバやゲートウェイの範疇だからです。詳細は「その他のHTTP機能」を参照ください。

バッファリングとストリーミング

一般的に、アプリが最良のスループットを発揮するのは、(適切なサイズの)出力をバッファリングして一度にすべて送信する場合です。これはZopeのような既存のフレームワークで一般的な方法です。出力はStringIOのようなオブジェクトに貯められ、レスポンスヘッダーとともに一挙に送信されます。

WSGIにおけるアプローチでは、アプリはバイト列のレスポンスbodyを、単一のイテラブル(リストなど)に投入して返却するだけです。この方法は、メモリに容易に収まる程度のHTMLを描画するような、アプリに大半の機能に適しています。

しかし巨大なファイルや、(multipartのserver pushなどの)HTTPストリーミングといった特殊用途の場合、アプリは小さなブロックに分けて出力しなくてはならない場合があります(たとえば巨大なファイルをメモリに読み込みたくないときなど)。また、レスポンスのうち一部の生成に時間がかかるが、他の部分は先に返してしまいたい場合もあります。

これらのケースでは、アプリはイテレータ(多くの場合ジェネレータ)を返し、それがブロックごとの内容を出力していくというのが一般的です。こうしたブロックはmultipart境界(server pushの場合)と同時に、あるいは時間のかかる作業(ディスク上の別のブロックを読み込むような)の直前に分割される可能性があります。

WSGIサーバ、ゲートウェイミドルウェアはどんなブロックの送信も 絶対に 遅らせてはいけません。ブロックをクライアントへすべて送信するか、アプリが次のブロックを生成している間も送信を続けることを かならず 保証しなければいけません。 サーバ(ゲートウェイ)またはミドルウェアは、この保証をするために次の3つの方法のうち1つを選ぶことができます。

  1. アプリにコントロールが移る前に、すべてのブロックをOSに送る(そしてすべてのO/Sバッファをフラッシュするよう要請する)。

  2. アプリが次のブロックを生成している間、別のスレッドでブロックの送信を続ける。

  3. ミドルウェアのみ)すべてのブロックを親ゲートウェイまたは親サーバに送る。

このような保証をすることで、WSGIはアプリの出力データ送信が特定の箇所で行き詰まらないようにできます。これは、たとえばmultipart server pushストリーミングのように、データをmultipart境界に挟んでクライアントに送信しなければいけない場合、正常な動作のために欠かせないことです。

ミドルウェアによるブロック境界の扱いかた

非同期のアプリとサーバをよりよくサポートするため、ミドルウェアは、アプリのイテラブルから複数の値を取得しようと待機しては 絶対に いけません。ミドルウェアが出力を行う前に、アプリからのデータを集積する必要がある場合、、かならず 空のバイト列をyieldする必要があります。

この要件を別の言い方にするならば、アプリが1つの値をyieldするたび、ミドルウェアかならず、最低でも1つの値をyieldしなければならない ということです。ミドルウェアがyieldする値を持たない場合、バイト列をyieldする必要があります。

この要件は、非同期アプリとサーバが協力し、同時に複数のアプリのインスタンスを走らせるときに必要なスレッドの数を削減するために必要です。

同時にこの要件は、ミドルウェアは、アプリが返却したイテラブルを受け取ったら、 かならず すぐにイテラブルを返却しなければいけない、ということも意味します。また、ミドルウェアが、アプリのyieldしたデータを送信するために write() callableを使うことは禁止されています。ミドルウェアが親サーバの write() callableを使ってデータを送信するのは、アプリもミドルウェアの提供した write() callableを使った場合のみです。

write() Callable

いくつかの既存のアプリフレームワークAPIは、WSGIとは違う方法でバッファリングしない出力をサポートしています。具体的には、バッファリングしないデータブロックを書き込むための「write」関数やメソッドを提供したり、または、バッファリングされた「write」関数とバッファのフラッシュを行うための「flush」機構を提供しています。

残念ながらこうしたAPIは、スレッドやその他の特殊な機構がない限り、WSGIアプリが返す「イテラブル」な値を使用して実装することはできません。

なのでこれらのフレームワークが重要なAPIを使用しつづけられるように、WSGIには、start_response から返却される write() という特別なcallableがあります。

新しいWSGIアプリやフレームは、必須でないのなら write() callableを使用する べきではありませんwrite() callableは厳密にいうと、どうしても必要なストリーミングAPIをサポートするための一種のハックです。一般的に、アプリは自分の出力をイテラブルとして返すべきです。こうすることで、webサーバと同じPythonスレッド内に別のタスクをはさむことができ、全体としてスループットの向上が期待できるからです。

write() callableは start_response() callableの返り値であり、1つの引数をとります。これはHTTPレスポンスbodyの一部として書き込まれるバイト列であり、イテラブルからyieldされた場合と完全に同じあつかいをうけます。言いかえると、 write() が返却されるよりも前に、渡されたバイト列はすべてクライアントに送られているか、アプリの処理が進む間に送信できるように、バッファに入れておく必要があるということです。

レスポンスbodyをすべて write() から生成するとしても、アプリは かならず イテラブルオブジェクトを返す必要があります。 返却されるイテラブルは空でも構いません [ may ] (つまり中身のあるバイト列をyieldしない)。しかし、もし中身のあるバイト列をyield する のであれば、その出力はサーバ(ゲートウェイ)に通常と同じようにあつかわれる必要があります(つまり送信されるか、即座にキューに入れられる必要がある)。アプリは、自分の返したイテラブルのなかから write()絶対に 実行してはいけません。なので、イテラブルからyieldされたバイト列は、write() に渡されたバイト列がすべてクライアントに送られた後に送信されます。

Unicode問題

HTTPがUnicodeを直接サポートしていないので、この規格も同様にサポートしません。エンコーディングやデコーディングはアプリによって処理される必要があります。つまり、サーバとやりとりする際の文字列の型は、かならず strbytes でなくてはならず、 unicode であってはいけません。文字列オブジェクトが必要な場所で unicode オブジェクトを使った際の挙動は定義されていません。

また、httpステータスやレスポンスヘッダーとして start_response() に渡される文字列は、 かならず RFC2616で指定されたエンコーディングに従う必要があります。 つまり、ISO-8859-1の文字か、RFC 2047 MIMEエンコーディングのどちらかでなくてはいけません。

strStringType 型をUnicodeベースであつかっているPythonプラットフォーム(たとえばJython, IronPython, Python3など)の場合、この規格書のなかで使用されているすべての「文字列」は、ISO-8859-1エンコーディング\u0000 から \n00FF まで)で表現できるコードポイントのみ使用できます。このほかのUnicode文字やコードポイントをふくむ文字列を渡すことは、アプリの致命的なエラーです。 同様に、サーバ(ゲートウェイ)も上記以外のUnicode文字を 絶対に 渡してはいけません。

繰り返しますが、この規格書で「文字列」として言及されているすべてのオブジェクトは かならず strStringType であり、絶対に unicodeUnicodeType ではありません。そして、たとえ使用しているプラットフォーム上で strStringType 8bitより大きい文字を使用できたとしても、この規格書における「文字列」は下位8bitのみを使用します。

この規格書で「バイト列」として言及されている値(つまり write() に渡される wsgi.input から読み出される値や、アプリからyieldされる値)については、かならず Python 3では bytes 、それより前のバージョンでは str でなければいけません。

エラー処理

一般的に、アプリは内部で発生したエラーを捕捉し、ブラウザ上に親切なメッセージを表示する べき です。(なにが「親切」かは状況に応じてアプリが決めることです。)

しかし、そのようなメッセージを表示するためには、すでにアプリがブラウザにデータを送ってしまっていては問題があります。レスポンスを破壊するおそれがあるからです。これを避けるため、WSGIは、アプリがエラーメッセージを送信するか、さもなければ自動的に停止する機構を提供します。 start_response に渡される exc_info のことです。以下がその使用例です。

try:
    # regular application code here
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
    #     in a separate handler before this bare 'except:'...
    status = "500 Oops"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers, sys.exc_info())
    return ["error body goes here"]

例外発生時にまだなにも出力されていない場合、 start_response の呼び出しは正常にリターンし、アプリはエラー内容をブラウザに送信をします。しかし、すでになんらかの出力がブラウザに送信されてしまっている場合、 start_response は渡された例外を再送出します。アプリの動作を中断させるため、この例外はアプリによって捕捉される べきではありません 。サーバ(ゲートウェイ)はこの(致命的な)例外を補足し、レスポンスを中断することができます。

サーバは、アプリ本体やアプリの返したイテレーションで発生した例外をすべて補足し、ロギングする べき です。もしアプリがエラーを発生させたとき、すでにレスポンスの一部がブラウザーまで届いていた場合、サーバ(ゲートウェイ)はエラー情報を出力に付けくわえても 構いません 。すでに送られたヘッダーが text/* のcontent typeを指定しており、サーバがただしく改変することができるならば可能です。

ミドルウェアによっては、追加の例外処理を提供したり、アプリ側のエラー情報をキャッチして置換したい場合もあると思います。そういった場合、ミドルウェアstart_resopnse に渡された exc_info を再送出する のではなく 、代わりにミドルウェアの規定した例外を投げたり、受け取った引数を保存したあとで例外を投げずリターンすることができます。こうすることで、ミドルウェアにエラー出力を改変させた上で、アプリはエラー内容のイテラブルを返す(あるいは write() を実行する)ことができます。こうした手法は、アプリが以下のような処理をしている限り有効です。

  1. エラーのレスポンスを始める際、常に exc_info を渡している。

  2. exc_info が渡される際、 start_resnponse で発生した例外を捕捉しない。

HTTP 1.1 Expect/Continue

HTTP 1.1を実装するサーバ(ゲートウェイ)は、 かならず HTTP 1.1 の「expect/continue」機構を明確にサポートをしなければいけません。これは以下のうちのどれかの方法で達成できます。

  1. Expect: 100-continue を含んだリクエスト群に対しては、即時に「100 Continue」レスポンスを返し、通常どおり処理を進める。

  2. リクエストの処理を通常どおり進めるが、アプリが最初に wsgi.inputh ストリームから読み込もうとしたとき、そのストリームから「100 Continue」レスポンスを送るようにする。その読み込みリクエストは、クライアントが応答するまでブロックされていなくてはならない。

  3. サーバが expect/continueに対応していないとクライアントがみなすまで待機し、自分のリクエストbodyをクライアントに送信する。(これは次善策であり、推奨されない。)

こうした挙動の制限はHTTP 1.0や、アプリオブジェクトに向けられていないリクエストには適用されないことに注意してください。HTTP 1.1 Expect/Continueの詳細については、RFC 2616, sections 8.2.3と10.1.1を参照ください。

HTTPのその他の機能

一般的に、サーバ(ゲートウェイ)は「黙って」いるべきで、出力はすべてアプリの支配下で完成させるものです。変更を加えることができるとすれば、アプリの応答の意味を大きく改変しない場合に限ります。アプリ開発者にとっては、さらなる機能を提供したい場合はいつでもミドルウェアを追加できます。なので、サーバ(ゲートウェイ)の開発者はそうした実装に慎重であるべきです。ある意味では、サーバは、自分自身をHTTPの「ゲートウェイサーバ」とみなすべきで、その場合アプリはHTTPの「オリジンサーバ」にあたります。(こうした用語の定義についてはRFC 2616, section 1.3を参照ください)

しかし、WSGIサーバとアプリはHTTPで通信するわけではないので、RFC 2616でいうところの「ホップバイホップ」ヘッダーはWSGIの内部通信には適用されません。WSGIアプリは、このようなヘッダーを生成する必要のあるHTTP機能を使ったり、 environ 辞書に含まれた「ホップバイホップ」ヘッダーに依拠しようとしては 絶対に いけません。 WSGIサーバは、送られてくる有効な「ホップバイホップ」ヘッダーを かならず 自分自身で処理する必要があります。たとえば、もし有効ならchunked encodingも含め、送られてきた Transfer-Encoding をすべてデコードするということです。

これらの原則をHTTPのさまざまな機能に適用することで、If-None_matchIf-Modified-Since といったリクエストヘッダーと Last-ModifiedETag といったレスポンスヘッダーを使ってキャッシュ確認を担当するのが おそらく サーバである、ということが明確になります。しかし、これに関しては絶対にそうする必要はなく、もしアプリ側で対応したいのであれば、アプリがキャッシュの確認を行う べき です。かならずしもサーバ(ゲートウェイ)がそうした確認をする必要はないからです。

同様に、サーバはアプリの応答の再エンコードやトランスポートエンコードをおこなうことが 可能 です。コンテンツエンコーディングはアプリが自分で行う べき ですが、トランスポートエンコーディング絶対に 適用してはいけません。クライアントからの要望があり、またアプリが自分自身でbyte rangesに対応していない場合、サーバはアプリのレスポンスのbyte rangesを送信する ことができます 。しかし繰り返しますが、望むのならばアプリ自身がこの機能を果たす べき です。

アプリに対するこれらの制限は、かならずしもすべてのアプリがすべてのHTTP機能を再実装しなければならないという意味ではありません。多くのHTTP機能は、その一部あるいは全部をミドルウェアで実装することができるので、サーバとアプリの両方の開発者は、同じ機能を何度も開発し直す必要はありません。

スレッドのサポート

スレッドに対応するかしないかはサーバによります。 複数のリクエストを並列で実行できるサーバでも、アプリをシングルスレッドで走らせるオプションを提供するべきです。そうすることで、スレッドセーフでないアプリやフレームワークもそのサーバを使用できるからです。

実装とアプリケーションに関して

サーバ拡張API

サーバによっては、アプリやフレームワークの開発者が特殊な用途で使えるような、さらに高度なAPIを提供したいかもしれません。 たとえば、 mod_python にもとづいたゲートウェイは、Apache APIの一部をWSGI拡張として提供したいかもしれません。

もっとも単純なケースでは、 mod_python.some_api のような environ 変数を定義する以上は必要ないかもしれません。しかし多くの場合、ミドルウェアの存在がものごとを複雑化します。 たとえば、environ 変数内の特定のHTTPヘッダーにアクセスできるAPIの場合、 environミドルウェアに改変されている可能性があります。

一般的に、WSGIの機能の一部分を重複させたり、置き換えたり、迂回するような拡張APIは、ミドルウェアとの互換性を損なうリスクがあります。サーバ(ゲートウェイ)の開発者は、誰もミドルウェアを使わないなどと推測してはいけません。なぜなら、いくつかのフレームワークは、そのほぼすべてを多様なミドルウェアの組み合わせとして構築・再構築して機能させているからです。

なので、最大限の互換性を提供するため、WSGIの機能の一部を置き換える拡張APIを提供しようとするサーバやゲートウェイは、その拡張APIを実行する際に、置き換えられた側のAPIの一部を使用するようにデザインしなければいけません [ must ] 。たとえば、HTTPリクエストヘッダーにアクセスする拡張APIは、アプリケーションに対し、現在の environ を渡すように強制する必要があります。こうすることで、サーバ/ゲートウェイは、API経由でアクセス可能なHTTPヘッダーがミドルウェアによって改変されていないことを検証できます。もし environ の中に入っているHTTPヘッダーを拡張APIが許容できない場合、アプリに対してサービスの提供を拒否する必要があります。たとえば例外を投げたり、ヘッダーのコレクションのかわりに None を返したり、APIに適した方法を取ります。

同様に、拡張APIが応答データやヘッダーを書き込むための代替手段を提供する場合、アプリ側に拡張サービスを使わせる前に、 start_response callbleを渡すように要求する必要があります。もし渡されたオブジェクトが、サーバ/ゲートウェイが最初にアプリに与えたものと異なった場合、正常な操作が保証されないため、拡張サービスの提供は拒否する必要があります。

こうしたガイドラインは、解析済みクッキー、フォーム変数、セッションのような environ を追加するミドルウェアにも適用されます。具体的には、このようなミドルウェアはこれらの機能を、 environ を操作する関数として提供するべきであり、単純に値を追加するべきではありません。これは、 environ からの情報の取得は、ミドルウェアによるURLの書き換えやその他の変更の 後に 行われるということを保証するためです。

こうした「安全な拡張」のルールを、サーバ/ゲートウェイ側とミドルウェア側の両方の開発者が尊重するのは非常に大切です。ミドルウェアの機能が environ の拡張APIによってバイパスされることを避けるために、開発者がすべての拡張API削除せざるをえない、というような未来は避けなくてはなりません。

アプリ設定

この規格書は、実行するアプリをサーバがどのように選んだり取得するか、ということは定義しません。こうした設定項目はそれぞれのサーバ固有の問題です。サーバ/ゲートウェイの開発者には、どのような方法で実行するアプリオブジェクトを設定するのか、またどのようなオプション(たとえばスレッドなど)があるのかをドキュメント化することが求められます。

逆にフレームワークの開発者は、フレームワークの機能を包み込むアプリオブジェクトをどのように生成するのかをドキュメント化する必要があります。サーバとアプリフレームワークを選択したユーザは、この2つを接続しなければいけません。しかし、いまフレームワークとサーバは共通のインターフェイスを持っているので、新たに触れるサーバ/フレームワークのペアであって、必要になるのは面倒な開発ではなく機械的な作業に過ぎないはずです。

最後に、特定のアプリやフレームワークミドルウェアenviron 辞書を使ってシンプルな文字列の設定オプションを受け取りたいはずです。サーバやゲートウェイは、アプリのデプロイ時に名前-値のペアを environ に挿入できるようにすることで、これをサポートする べき です。もっとも単純なやりかたは、OSの提供する環境変数をただ os.environ から environ 辞書にコピーするというものです。デプロイをする際はふつう外部からサーバを設定できますし、CGIの場合もサーバの設定ファイルからセットできるはずです。

アプリはこのような必須の変数を最小限に抑える べき です。なぜならすべてのサーバが簡単な設定手段を持っているとは限らないからです。最悪のケースでは、デプロイ担当者が必要な設定値をスクリプト内で書き加えるということになります。

from the_app import application

def new_app(environ, start_response):
    environ['the_app.configval1'] = 'something'
    return application(environ, start_response)

ただ、ほとんどの既存アプリやフレームワークでは、固有の設定ファイルのパスを示す値のみが必要になるはずです。(もちろん、アプリ実行ごとに読み込みが行われないように、設定値をキャッシュしておく必要があります。)

URLの再構築

リクエストされたURLをアプリが完全に再構築する場合、Ian Bickingの提案する以下のアルゴリズムを使用するといいでしょう。

from urllib.parse import quote
url = environ['wsgi.url_scheme']+'://'

if environ.get('HTTP_HOST'):
    url += environ['HTTP_HOST']
else:
    url += environ['SERVER_NAME']

    if environ['wsgi.url_scheme'] == 'https':
        if environ['SERVER_PORT'] != '443':
            url += ':' + environ['SERVER_PORT']
    else:
        if environ['SERVER_PORT'] != '80':
            url += ':' + environ['SERVER_PORT']

url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
    url += '?' + environ['QUERY_STRING']

このように再構築されたURLが、クライアントからのリクエストとかならずしも完全に一致するとは限らない、ということには注意してください。たとえばサーバが書き換えを行っており、クライアントのもともとのURLを正規化しているかもしれません。

プラットフォーム依存の任意のファイル操作

特定のOS環境は特殊な高性能ファイル送信機能を備えています。たとえばUnixにおける sendfile() システムコールなどがそうです。 サーバやゲートウェイはこの機能を environ 内の wsgi.file_wrapper として提供してもいい [ may ] でしょう。アプリはこの「file wrapper」を使用して、fileやfile-likeオブジェクトをイテラブルに変換して返してもいい [ may ] でしょう。

if 'wsgi.file_wrapper' in environ:
    return environ['wsgi.file_wrapper'](filelike, block_size)
else:
    return iter(lambda: filelike.read(block_size), '')

サーバやゲートウェイwsgi.file_wrapper を提供する場合、それは1つの必須位置引数と1つの任意位置引数を取るcallableでなければいけません。ひとつめの引数が送信されるfile-likeオブジェクトで、2つ目がブロックサイズの「提案」です(つまりサーバ/ゲートウェイはかならずしも使う必要がありません)。このcallableは かならず イテラブルオブジェクトを返さなければいけませんし、サーバ/ゲートウェイがアプリから実際に戻り値を受け取るまでは、 絶対に データの送信を行ってはいけません。 (そうしてしまった場合、ミドルウェアが応答データを解析したり上書きすることを妨げる可能性があります。)

「file-like」であるためには、アプリから返されるそのオブジェクトは、1つの任意のsize引数を取る read()メソッドを持っている必要があります。 close() を持っていても かまわない です。その場合、 wsgi.file_wrapper から返されるイテラブルは、file-likeオブジェクトが持っていたもともとの close() を呼ぶ close() メソッドを かならず 実装していなくてはいけません。もし「file-like」オブジェクトが、Pythonの組み込みのfileオブジェクトのメソッドやアトリビュートと同じもの(たとえば fileno())を持っていた場合、 wsgi.file_wrapper はこれらが組み込みのfileオブジェクトと同じ意味を持っているとみなして かまいません

プラットフォーム依存のファイル操作は、かならずアプリが実行を終えて、サーバやゲートウェイがwrapperオブジェクトが返却されたことを確認した あとで 行われなければいけません。(繰り返しになりますが、ミドルウェアやエラー処理なども存在するため、wrapperが使用されることが保証されるわけではありません。)

close() に限らず、アプリから返ってくるfile wrapperは iter(filelike.read,) が返却された場合と同じ意味を持つと考えるべきです。言いかえれば、ファイル送信は、送信が始まった時点での「file」の現在位置から始まり、終端に達するまで、あるいは Content-Length のデータが書き込まれるまで続きます。(もしアプリが Content-Length を提供しない場合、サーバはfileの実装の情報をもとに、自分で生成しても かまいません 。)

もちろん、プラットフォーム依存のファイル送信APIは、一般的に任意の「file-like」オブジェクトを受け付けません。なので wsgi.file_wrapper は、渡されたオブジェクトの fileno() (Unix系OS)や java.nio.FileChannelJythonの場合)などを分析し、そのfile-likeオブジェクトがプラットフォーム依存APIの使用に適しているかを判断する必要があります。

仮にそのオブジェクトがプラットフォーム向けAPIに適して いない 場合でも、 wsgi.file_wrapperかならず read()close() を包み込むイテラブルを返却し、file wrapperを使用するアプリがプラットフォームを越えて移植可能であるようにしなければいけません。以下は、Python2.2未満と新しいバージョンで同じように動作する、非プラットフォーム依存のfile wrapperクラスです。

class FileWrapper:

    def __init__(self, filelike, blksize=8192):
        self.filelike = filelike
        self.blksize = blksize
        if hasattr(filelike, 'close'):
            self.close = filelike.close

    def __getitem__(self, key):
        data = self.filelike.read(self.blksize)
        if data:
            return data
        raise IndexError

そして、サーバ/ゲートウェイは以下のコードで上記のクラスを使用し、プラットフォーム依存APIにアクセスできるようにします。

environ['wsgi.file_wrapper'] = FileWrapper
result = application(environ, start_response)

try:
    if isinstance(result, FileWrapper):
        # check if result.filelike is usable w/platform-specific
        # API, and if so, use that API to transmit the result.
        # If not, fall through to normal iterable handling
        # loop below.

    for data in result:
        # etc.

finally:
    if hasattr(result, 'close'):
        result.close()