1993年、ウェブはまだ揺籃期にあり、 1,400万人のユーザーと100のウェブサイト 。ページは静的でしたが、最新のニュースやデータなどの動的なコンテンツを作成する必要がすでにありました。これに応えて、Rob McCoolと他の貢献者は、National Center for Supercomputing Applications(NCSA)にCommon Gateway Interface(CGI)を実装しました。 HTTPdWebサーバー (Apacheの前身)。これは、別のアプリケーションによって生成されたコンテンツを提供できる最初のWebサーバーでした。
それ以来、インターネット上のユーザー数は爆発的に増加し、動的なWebサイトは至る所に存在するようになりました。開発者は、新しい言語を最初に学習するとき、またはコードを最初に学習するときでさえ、すぐにコードをWebにフックする方法について知りたいと思っています。
CGIの作成以来、多くの変化がありました。 CGIアプローチは、要求ごとに新しいプロセスを作成する必要があり、メモリとCPUを浪費するため、実用的ではなくなりました。 FastCGI](http://www.fastcgi.com/)(1996)やなど、他のいくつかの低レベルのアプローチが登場しました。 mod_python (2000)、PythonWebフレームワークとWebサーバーの間に異なるインターフェースを提供します。さまざまなアプローチが急増するにつれて、開発者によるフレームワークの選択がWebサーバーの選択を制限することになり、その逆も同様でした。
この問題に対処するために、2003年にPhillip J.Ebyは提案しました PEP-0333 、Python Web Server Gateway Interface(WSGI)。アイデアは、PythonアプリケーションとWebサーバーの間に高レベルのユニバーサルインターフェイスを提供することでした。
2003年、 PEP-3333 WSGIインターフェイスを更新して、Python3のサポートを追加しました。現在、ほとんどすべてのPythonフレームワークは、Webサーバーと通信するための手段としてWSGIを使用しています。こうやって Django 、 フラスコ そして他の多くの人気のあるフレームワークがそれを行います。
この記事は、WSGIがどのように機能するかを読者に垣間見せ、読者が単純なWSGIアプリケーションまたはサーバーを構築できるようにすることを目的としています。ただし、これは網羅的なものではありません。本番環境に対応したサーバーまたはアプリケーションを実装する予定の開発者は、 WSGI仕様 。
WSGIは、サーバーとアプリケーションが準拠する必要のある単純なルールを指定します。この全体的なパターンを確認することから始めましょう。
Python 3.5では、アプリケーションインターフェイスは次のようになります。
def application(environ, start_response): body = b'Hello world!
' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body]
Python 2.7では、このインターフェースはそれほど変わりません。唯一の変更点は、本体がstr
で表されることです。 bytes
の代わりにオブジェクト1。
この場合は関数を使用しましたが、 呼び出し可能 しましょう。ここでのアプリケーションオブジェクトのルールは次のとおりです。
environ
で呼び出し可能である必要がありますおよびstart_response
パラメーター。 start_response
を呼び出す必要があります本文を送信する前にコールバックします。 これらのルールを満たし、同じ効果を生み出すオブジェクトの別の例は次のとおりです。
class Application: def __init__(self, environ, start_response): self.environ = environ self.start_response = start_response def __iter__(self): body = b'Hello world!
' status = '200 OK' headers = [('Content-type', 'text/plain')] self.start_response(status, headers) yield body
WSGIサーバーは、次のようにこのアプリケーションとインターフェイスする場合があります。
def write(chunk): '''Write data back to client''' ... def send_status(status): '''Send HTTP status code''' ... def send_headers(headers): '''Send HTTP headers''' ... def start_response(status, headers): '''WSGI start_response callable''' send_status(status) send_headers(headers) return write # Make request to application response = application(environ, start_response) try: for chunk in response: write(chunk) finally: if hasattr(response, 'close'): response.close()
お気づきかもしれませんが、start_response
callableはwrite
を返しましたアプリケーションがクライアントにデータを送り返すために使用する可能性がある呼び出し可能ですが、アプリケーションのコード例では使用されていません。これwrite
インターフェイスは非推奨であり、今のところ無視できます。これについては、この記事の後半で簡単に説明します。
サーバーの責任のもう1つの特徴は、オプションのclose
を呼び出すことです。応答イテレータのメソッド(存在する場合)。グラハム・ダンプルトンの記事で指摘されているように ここに 、これはWSGIの見過ごされがちな機能です。このメソッドを呼び出すと、 存在する場合 、アプリケーションがまだ保持している可能性のあるリソースを解放できるようにします。
environ
引数environ
パラメータは辞書オブジェクトである必要があります。これは、CGIとほぼ同じ方法で、要求およびサーバー情報をアプリケーションに渡すために使用されます。実際、すべてのCGI環境変数はWSGIで有効であり、サーバーはアプリケーションに適用されるすべてのものを渡す必要があります。
渡すことができるオプションのキーはたくさんありますが、いくつかは必須です。例として、次のGET
リクエスト:
$ curl 'http://localhost:8000/auth?user=obiwan&token=123'
これらはサーバーが使用するキーです しなければならない 提供し、それらが取る値:
キー | 値 | コメント |
---|---|---|
REQUEST_METHOD | 'GET' | |
SCRIPT_NAME | '' | サーバーのセットアップに依存 |
PATH_INFO | '/auth' | |
QUERY_STRING | 'token=123' | |
CONTENT_TYPE | '' | |
CONTENT_LENGTH | '' | |
SERVER_NAME | '127.0.0.1' | サーバーのセットアップに依存 |
SERVER_PORT | '8000' | |
SERVER_PROTOCOL | 'HTTP/1.1' | |
HTTP_(...) | クライアント提供のHTTPヘッダー | |
wsgi.version | (1, 0) | WSGIバージョンのタプル |
wsgi.url_scheme | 'http' | |
wsgi.input | ファイルのようなオブジェクト | |
wsgi.errors | ファイルのようなオブジェクト | |
wsgi.multithread | False | True サーバーがマルチスレッドの場合 |
wsgi.multiprocess | False | True サーバーが複数のプロセスを実行する場合 |
wsgi.run_once | False | True サーバーがこのスクリプトが1回だけ実行されることを期待している場合(例:CGI環境で) |
この規則の例外は、これらのキーの1つが空の場合(上記の表のCONTENT_TYPE
のように)、辞書から省略でき、空の文字列に対応していると見なされることです。
wsgi.input
およびwsgi.errors
ほとんどenviron
キーは単純ですが、そのうちの2つはもう少し明確にする必要があります。wsgi.input
はクライアントからのリクエスト本文を含むストリームを含む必要があり、wsgi.errors
はアプリケーションが発生したエラーを報告します。アプリケーションからwsgi.errors
に送信されたエラー通常、サーバーのエラーログに送信されます。
これらの2つのキーには、ファイルのようなオブジェクトが含まれている必要があります。つまり、Pythonでファイルまたはソケットを開いたときに取得するオブジェクトと同じように、ストリームとして読み取りまたは書き込みを行うインターフェイスを提供するオブジェクトです。これは最初は難しいように思えるかもしれませんが、幸いなことに、Pythonはこれを処理するための優れたツールを提供してくれます。
まず、どのようなストリームについて話しているのですか? WSGIの定義によると、wsgi.input
およびwsgi.errors
bytes
を処理する必要がありますPython3およびstr
のオブジェクトPython 2のオブジェクト。いずれの場合も、メモリ内バッファを使用してWSGIインターフェイスを介してデータを渡したり取得したりする場合は、クラスio.BytesIO
を使用できます。
例として、WSGIサーバーを作成している場合、次のようにアプリケーションにリクエスト本文を提供できます。
import io ... request_data = 'some request body' environ['wsgi.input'] = io.BytesIO(request_data)
import io ... request_data = 'some request body'.encode('utf-8') # bytes object environ['wsgi.input'] = io.BytesIO(request_data)
アプリケーション側では、受け取ったストリーム入力を文字列に変換する場合は、次のように記述します。
readstr = environ['wsgi.input'].read() # returns str object
readbytes = environ['wsgi.input'].read() # returns bytes object readstr = readbytes.decode('utf-8') # returns str object
wsgi.errors
ストリームはアプリケーションエラーをサーバーに報告するために使用する必要があり、行は
で終了する必要があります。 Webサーバーは、システムに応じて終了する別の行への変換を処理する必要があります。
start_response
引数start_response
引数は、2つの必須引数、つまりstatus
を持つ呼び出し可能である必要があります。およびheaders
、および1つのオプションの引数exc_info
。本体の一部がWebサーバーに返送される前に、アプリケーションによって呼び出される必要があります。
この記事の冒頭にある最初のアプリケーション例では、応答の本文をリストとして返したため、リストがいつ繰り返されるかを制御することはできません。このため、start_response
に電話する必要がありましたリストを返す前に。
2つ目では、start_response
と呼びました応答本体の最初の(この場合は唯一の)部分を生成する直前。どちらの方法もWSGI仕様内で有効です。
Webサーバー側から、start_response
の呼び出し実際にヘッダーをクライアントに送信するべきではありませんが、クライアントに送り返すために応答本文に空でないバイト文字列が少なくとも1つあるまでヘッダーを遅らせます。このアーキテクチャにより、アプリケーションの実行の最後の可能な瞬間まで、エラーを正しく報告できます。
status
start_response
の引数status
start_response
に渡される引数コールバックは、HTTPステータスコードと説明で構成され、単一のスペースで区切られた文字列である必要があります。有効な例は、'200 OK'
または'404 Not Found'
です。
headers
start_response
の引数headers
start_response
に渡される引数コールバックはPythonでなければなりませんlist
tuple
sの、各タプルは(header_name, header_value)
として構成されます。各ヘッダーの名前と値は両方とも文字列である必要があります(Pythonのバージョンに関係なく)。これは、WSGI仕様で実際に要求されているため、タイプが重要となるまれな例です。
これは、header
の有効な例です。引数は次のようになります。
response_body = json.dumps(data).encode('utf-8') headers = [('Content-Type', 'application/json'), ('Content-Length', str(len(response_body))]
HTTPヘッダーでは大文字と小文字が区別されません。また、WSGI準拠のWebサーバーを作成している場合は、これらのヘッダーを確認するときに注意する必要があります。また、アプリケーションによって提供されるヘッダーのリストは網羅的ではありません。応答をクライアントに送り返す前に、必要なすべてのHTTPヘッダーが存在することを確認し、アプリケーションによって提供されていないヘッダーを入力するのはサーバーの責任です。
exc_info
start_response
の引数start_response
コールバックは、エラー処理に使用される3番目の引数exc_info
をサポートする必要があります。この引数の正しい使用法と実装は、本番Webサーバーとアプリケーションにとって最も重要ですが、この記事の範囲外です。
詳細については、WSGI仕様で入手できます。 ここに 。
start_response
戻り値– write
折り返し電話下位互換性のために、WSGIを実装するWebサーバーはwrite
を返す必要があります。呼び出し可能。このコールバックにより、アプリケーションは、イテレータを介してサーバーにデータを渡すのではなく、本文の応答データをクライアントに直接書き戻すことができます。
その存在にもかかわらず、これは非推奨のインターフェースであり、新しいアプリケーションはそれを使用しないようにする必要があります。
llc c vs llc s
WSGIを実装するアプリケーションは、反復可能なオブジェクトを返すことによって応答本文を生成する必要があります。ほとんどのアプリケーションでは、応答本体はそれほど大きくなく、サーバーのメモリに簡単に収まります。その場合、それを送信する最も効率的な方法は、1つの要素を反復可能にして一度に送信することです。全身をメモリにロードすることが不可能な特殊なケースでは、アプリケーションはこの反復可能なインターフェイスを介して部分的にそれを返す場合があります。
ここでは、Python2とPython3のWSGIの違いはわずかです。Python3では、応答の本文はbytes
で表されます。オブジェクト; Python 2では、これの正しいタイプはstr
です。
UTF-8文字列をbytes
に変換するまたはstr
簡単な作業です:
body = 'unicode stuff'.encode('utf-8')
body = u'unicode stuff'.encode('utf-8')
Python 2のUnicodeとバイト文字列の処理について詳しく知りたい場合は、次のチュートリアルをご覧ください。 Youtube 。
WSGIを実装するWebサーバーもwrite
をサポートする必要があります上記のように、下位互換性のためのコールバック。
このシンプルなインターフェイスを理解すれば、実際にサーバーを起動しなくても、アプリケーションをテストするためのスクリプトを簡単に作成できます。
この小さなスクリプトを例にとってみましょう。
from io import BytesIO def get(app, path = '/', query = ''): response_status = [] response_headers = [] def start_response(status, headers): status = status.split(' ', 1) response_status.append((int(status[0]), status[1])) response_headers.append(dict(headers)) environ = { 'HTTP_ACCEPT': '*/*', 'HTTP_HOST': '127.0.0.1:8000', 'HTTP_USER_AGENT': 'TestAgent/1.0', 'PATH_INFO': path, 'QUERY_STRING': query, 'REQUEST_METHOD': 'GET', 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'TestServer/1.0', 'wsgi.errors': BytesIO(b''), 'wsgi.input': BytesIO(b''), 'wsgi.multiprocess': False, 'wsgi.multithread': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0), } response_body = app(environ, start_response) merged_body = ''.join((x.decode('utf-8') for x in response_body)) if hasattr(response_body, 'close'): response_body.close() return {'status': response_status[0], 'headers': response_headers[0], 'body': merged_body}
このようにして、たとえば、いくつかのテストデータとモックモジュールをアプリに初期化し、GET
を作成することができます。それに応じて応答するかどうかをテストするために呼び出します。これは実際のウェブサーバーではないことがわかりますが、アプリケーションにstart_response
を提供することで、同等の方法でアプリとやり取りします。コールバックと環境変数を含む辞書。リクエストの最後に、レスポンスボディのイテレータを使用して、すべてのコンテンツを含む文字列を返します。同様のメソッド(または一般的なメソッド)は、さまざまなタイプのHTTP要求に対して作成できます。
この記事では、WSGIがファイルのアップロードを処理する方法については触れていません。これは、紹介記事には適さない、より「高度な」機能と見なされる可能性があるためです。それについてもっと知りたい場合は、 ファイル処理に言及しているPEP-3333セクション 。
この記事が、PythonがWebサーバーとどのように通信するかをよりよく理解するのに役立ち、開発者がこのインターフェイスを面白くて創造的な方法で使用できるようになることを願っています。
編集者に感謝します ニック・マクリー この記事を手伝ってくれて。彼の仕事のおかげで、元のテキストははるかに明確になり、いくつかのエラーは修正されませんでした。