WSDIウェブサービスゲートウェイプロトコル(1)

10466 ワード

序文
この文書は次のとおりです.https://www.python.org/dev/peps/pep-0333
注意:Python 3のサポートを確認する必要がある場合.xバージョンの更新(コミュニティエラー、付録、説明を含む)は、PEP 3333を参照してください.
サマリ
このドキュメントでは、一連のウェブサーバ間でのネットワークアプリケーションの移植性を向上させるために、ウェブサービス側とpythonウェブアプリケーションまたはフレームワークの標準インタフェースを示します.
理由と目標
Python言語には現在、Zope、Quixote、Webware、SkunkWeb、PSO、Twisted Webなどの多くのネットワークアプリケーションフレームワークが現れており、ここではPython Wikiの一部だけがリストされています.このような多くの選択はPythonの初心者にとって難題かもしれない.一般的に、Webフレームワークの選択はWebサーバの選択をある程度制限しているため、逆も同様である.
対照的に、Javaはこのように多くのWebアプリケーションフレームワークを使用することができるが、サーブレットAPIの使用により、サーブレットAPIをサポートするすべてのネットワークアプリケーションフレームワークが、同じようにサーブレットをサポートするネットワークサーバ上を走ることができる.
このようなPythonをサポートするAPIも広く利用されていれば(サーバがPython-Medusaであれ、組み込み型Python-mod_pythonであれ、ゲートウェイプロトコルでPython-CGI、FastCGIなどを呼び出すことにかかわらず)、フレームワークとWebサーバの選択を分けて、ユーザが望むサーバやwebフレームワークを自由に組み合わせることができるようになり、また、フレームワークとサーバ開発者を解放し、お気に入りの専門分野に集中させます.
このPEPドキュメントでは,サーバ側とアプリケーションフレームワーク間の単純で汎用的なインタフェース,Python Webサービスゲートウェイプロトコルインタフェース(WSDI)を提案した.
しかし、WSGI仕様の存在は、既存のサーバおよびアプリケーションフレームワークの状態を変更することはできません.サーバおよびフレームワークの作成者とメンテナンス者は、WSGIを確実に実現しなければ有効になりません.
ただし、WSGI仕様をサポートするサーバやフレームワークは現在存在しないため、WSGI仕様の実装やサポートを準備している著者にとっても即時の報酬はありません.そのため,WSGIは容易に実現されなければならず,開発者の実施時の初期投資も低い合理的な範囲で実現できる.
したがって,サービス側とフレームワーク側の実装の単純性はWSGIインタフェースの実用性よりも重要であり,これも任意の設計決定を行う際の最も重要な原則である.
しかし、フレームワークの作成者がフレームワークの単純性を実現することは、アプリケーションの作成者の単純性を意味するものではありません.これは別です.WSDIは、応答オブジェクトやクッキー処理のような派手なものが既存のフレームワークを阻害するだけなので、フレームワークの作成者には修飾されていないインタフェースを提供しています.WSGIの目標は、新しいWebフレームワークを作成するのではなく、既存のサーバとアプリケーションフレームワークの相互接続を促進することであることを強調する必要があります.
また、このターゲットは、すでに導入されているPythonバージョンでは使用できないものも回避します.そのため、新しい標準ライブラリモジュールはこの仕様に必須ではなく、Pythonのバージョンが2.2.2より大きいことも要求されません.(これは良いアイデアで、将来のPythonバージョンの標準ライブラリのWeb serverにもこのインタフェースのサポートが含まれます).
既存および将来のフレームワークおよびサーバを容易に実装できるほか、リクエストプリプロセッサ、応答プロセッサ、およびWSDIベースの他のミドルウェアコンポーネントを容易に作成する必要があります.ミドルウェアコンポーネントは、このミドルウェアを含むサーバにとって使用され、ミドルウェアに含まれるアプリケーションにとってサーバです.
ミドルウェアが単純で十分丈夫であるだけでなく、WSGIがサービス側およびフレームワークで広く応用されている場合、新しいPython webアプリケーションフレームワークが現れます.緩やかに結合されたWSGIミドルウェアからなるフレームワーク構造です.明らかに、既存のフレームワークの著者は、既存のサービスを再構築することを選択することができ、全体的なフレームワークではなく、WSDIを使用するライブラリのようになります.これにより、アプリケーション開発者は、単一のフレームワークのすべてのメリットとデメリットを提出するのではなく、最適なコンポーネントの組み合わせを選択して特定の機能を提供することができます.
もちろん、本稿までは、そのターゲットからまだ遠く、現在のWSGIの短期的な目標は、任意のフレームワークを任意のサーバ上で実行できるようにすることです.
最後に、現在のバージョンのWSGIでは、アプリケーションを1つのサーバまたはゲートウェイで配置する仕様が規定されていません.現在、これらはサービス側で定義され、実現されなければなりません.WSGIを実現し、サービス配置で一定の経験を積んだ後、別のPEPドキュメントを作成することができます.WSDIサーバおよびアプリケーションフレームワークの導入基準について説明します.
概要
WSDIインタフェースには、サービスまたはゲートウェイ、アプリケーション、またはフレームワークの2つの側面があります.サービス側はアプリケーション側が提供する呼び出し可能なオブジェクトを呼び出し、どのようにオブジェクトを提供するかの詳細はサーバまたはゲートウェイに依存します.一部のサーバまたはゲートウェイでは、アプリケーションのデプロイメント者がサーバまたはゲートウェイのインスタンスを作成し、アプリケーションオブジェクトを提供するための短いスクリプトを作成する必要がある場合があります.他のサーバでは、プロファイルまたは他のメカニズムを使用して、アプリケーション・オブジェクトをどこから導入するか、または取得するかを定義する必要がある場合があります.
サービス側とアプリケーション側を単一化するほか、両端仕様に従うミドルウェアを作成することもできます.これらのコンポーネントは、それらを含むサーバのアプリケーションとして機能してもよいし、アプリケーションを含むサーバとして機能してもよいし、拡張API、コンテンツ変換、ナビゲーション、その他の有用な機能を提供するために使用してもよい.
この仕様全体で、「関数、メソッド、クラス、またはcallメソッドを有するインスタンス」を表すために、用語「呼び出し可能」を使用します.サービス・エンド、ゲートウェイ、またはアプリケーションのニーズを満たすために、適切なテクノロジーで実現することを選択できます.逆に言えば、呼び出し可能なオブジェクトを呼び出すサーバでは、ゲートウェイやアプリケーションは呼び出し可能なオブジェクトタイプに依存することはできません.呼び出し可能なオブジェクトは呼び出され、内省することはできません.
アプリケーション・エンド/フレーム・エンド
アプリケーション・オブジェクトは、2つのパラメータを簡単に受信できる呼び出し可能なオブジェクトです.オブジェクトという言葉は、本当のオブジェクト・インスタンスが必要だと誤解するべきではありません.関数、メソッド、クラス、またはcallメソッドを持つインスタンスは、アプリケーション・オブジェクトとして受け入れられます.ほとんどのサーバまたはゲートウェイ(CGIを除く)が重複するリクエストを行うため、アプリケーション・オブジェクトも複数回呼び出す必要があります.
注意:私たちはそれを「アプリケーション」の対象と呼んでいますが、これはアプリケーション開発者がWeb開発プログラミングのAPIとしてWSDIを直接使用すべきであることを意味するものではありません.アプリケーション開発者は既存の、より抽象的なフレームワークに基づいて開発されていると仮定しています.WSGIはサーバとフレームワーク開発者のツールであり、アプリケーション開発者を直接サポートするわけではありません.
ここには、関数と呼び出し可能なクラスの2つの適用オブジェクトの例があります.
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リクエストごとに、サーバはアプリケーションを呼び出して処理します.説明のために、ここでは簡単なCGIゲートウェイがあり、アプリケーションオブジェクトを受信する関数を実現している.なお、この簡単な例は限られたエラー処理のみであり、実際にはキャプチャされていない異常に対してはsysがデフォルトである.stderrは出力され、webサーバに記録される.
import os, sys

def run_with_cgi(application):

    environ = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    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):
        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
             sys.stdout.write('Status: %s\r
' % status) for header in response_headers: sys.stdout.write('%s: %s\r
' % header) sys.stdout.write('\r
') sys.stdout.write(data) sys.stdout.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[0], exc_info[1], exc_info[2] finally: exc_info = None # avoid dangling circular ref elif headers_set: raise AssertionError("Headers already set!") headers_set[:] = [status, response_headers] 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()

ミドルウェア:サービス側とアプリケーション側のコンポーネントを実現
同じオブジェクトがサーバの役割を果たすか、アプリケーションの役割を果たすかに注目してください.このようなオブジェクトはミドルウェアと呼ばれ、以下の機能を実現することができます.
  • environオブジェクトを書き換えることにより、ターゲットURLに基づいて要求を異なるアプリケーションオブジェクトにルーティングすることができる.
  • は、同じプロセスにおいて複数のアプリケーションまたはフレームワークを並列に
  • させることを可能にする.
  • 要求転送と応答により、負荷等化とリモート処理
  • を実現する.
  • コンテンツの後処理、例えばXSLスタイルテーブル
  • を適用する.
    一般に、ミドルウェアの存在は、インタフェースの「サーバ/ゲートウェイ」および「アプリケーション/フレームワーク」に対して透過的であり、特にサポートする必要はありません.1人のユーザーがミドルウェアを統合するには、アプリケーションとしてサーバに提供し、ミドルウェアをサーバと見なし、それを利用して本当のアプリケーションを呼び出す必要があります.もちろん、「アプリケーション」は、複数のミドルウェアからなるスタックをミドルウェアスタックと呼ぶアプリケーションをパッケージした別のミドルウェアである可能性もあります.
    ほとんどの場合、ミドルウェアは、サービス側とアプリケーション側の両方の制限を同時に遵守し、両方のニーズを満たす必要があります.しかし、場合によっては、単純なサーバやアプリケーションよりもミドルウェアのニーズが厳しく、ミドルウェアのドキュメントに提出する必要があります.
    この(不真面目な)ミドルウェアコンポーネントは、応答タイプがtext/plainのテキストをJoe Stroutのpiglatinである.pyはpig latin文に変換され、(「真」のミドルウェアは、コンテンツタイプをより丈夫な方法で検査し、コンテンツコードを検査する可能性があります.また、この簡単な例では、単語が境界で分割されている場合も無視されます)
    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 string, 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())
            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))
                    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)