python wsgirefソース解析
python web開発においてhttpが要求する処理の流れは、一般的にweb-browser,web-server,wsgiとweb-apprationの四つの段階で、bottleによるweb-apprationを学習しました。http.serverも勉強しました。python 3のソースコードの中で持参したwsgirefの倉庫を完成したら、最後の一環であるwsgiをつなぎ合わせることができます。本論文は次のいくつかの部分に分けられます。 wsgiに関する概念 cgi例 wsgirefソース wsgi小结 小技
wsgiに関する概念
CGI
CGI(Common Gateway Interface)汎用ゲートウェイインターフェース。1993年に米国NCSAによって発明された。簡単で使いやすい、言葉に関係ないという特徴があります。今日はCGIを直接使ってプログラミングする人が少なくなりましたが、Apache、IIS、NgixなどのWebサーバーが主流です。
CGIはインタフェース規範を提供して、アプリケーションを提供して、普通は各種のシナリオ言語で、たとえばperl、php、pythonなどはウェブサービスを拡張して、サービスを動態化させます。
WSGI
WSGI(Web Server Gateway Interface)ウェブサービスゲートウェイインターフェース。は、ウェブサービスとウェブアプリケーションとの間のインターフェース仕様であり、PEP 333で提案されている。
wsgiはアプリケーションとウェブサービスの間を結合させ、アプリケーションは規範に従うだけで、様々なウェブサービスの配置で実行することができる。例えば、上の図では、flashk/djangoに基づいて実現されたアプリケーションは、gnicorn展開を使用しても良いし、nginx+uwsgi配置を使用しても良い。
ASGI
ASGI(Aynchronous Server Gateway Interface)非同期サーバゲートウェイインターフェース。ASGIは、非同期機能を持つPython Webサーバを目指し、フレームワークとアプリケーション間で標準インターフェースを提供する。ASGIはWSGI後方互換性の実現と複数のサーバとアプリケーションのフレームを持っています。
wsgiでは、要求応答モデルを使用して、各要求は同期して1つの応答を得ることができる。ASGIでは、要求の応答は非同期的に実現され、一般的にはwebsocketプロトコルに用いられる。asgiの内容は、非同期的な実現に関連しているので、本論文では多く紹介しない。
cgiの例
単純な概念の理解は難しいです。例に合わせて勉強します。まずCGIから始めます。
httpモジュールは簡単なファイルディレクトリサービスを提供します。
cgiスクリプト
cgi-binディレクトリを作成します。これはCGIに約束されたディレクトリ名です。そしてハロー.pyを作成します。コードは以下の通りです。
ソースここです
chmod 755ハロー.py
cgiサービスの実現
cgiの実現は主に以下のコードです。 subprocess.Popenを使用して、新しいプロセスを実行してスクリプト を実行します。リダイレクトスクリプトの出力は現在のsocketのwfile、つまりhttp要求のリターンになります。
コードも検証しました。なぜhello.pyに実行可能な権限を与える必要がありますか?
例から分かるように、http.serverはhttpサービスの提供に専念し、ap.pyは業務機能に専念し、両者はcgiで接続する。
wsgiref
wsgirefは、pythonが持参するwsgiの実現のための参考(reference)であり、主なコード構造:
ファイル
説明
handles.py
wsgi実現
headers.py
http-headerを管理する
シンプル_server.py
wsgiのhttpサービスをサポートします。
util.py&validator.py
ツールと検証
WSGIServerのコード: environ を作成します。 Server Handlerオブジェクトを作成する は、appオブジェクト を作成する。実行アプリ environ処理は主にhttp要求のheader情報をwsgi-serverの環境変数に付随する。セットアップ_environ環境変数の構築を継続する http要求をapplicationで処理するための戻り値 http応答を完了しました。
セットアップenvironはenvに対してさらに包装を行い、要求のin/errorが付いています。これによってenvを使ってhttpに対して読み書きを要求することができます。
アプリはまた、フィードバック関数start_を受信します。レスポンスは主にhttpプロトコルの仕様に従い、レスポンス状態とレスポンスを生成します。header:
簡単で簡単な小さい結び目のwsgiの実現。http要求の処理フローweb-browser<->web-server<->wsgi<->web-appicationでは、階層的な思想が具現されており、それぞれの層が異なることをする: web-serverはhttp/tcpプロトコルを処理して、スレッド/プロセスのスケジュールなどの下の階は を実現します。 wsgiはhttpの要求を受けて、appliaitonの処理要求を呼び出して、応答 を完成します。 appicationは、上位業務ロジック を処理する。
テクニック
wsgirefコードの中にも様々な小さい技術があります。勉強したらコードをもっとpythonicにします。
環境変数はこのように設定されています。
例えばストリームの継続書込み:
以上がpython wsgirefソースの解析の詳細です。python wsgirefソースに関する詳細については、他の関連記事に注目してください。
CGI
CGI(Common Gateway Interface)汎用ゲートウェイインターフェース。1993年に米国NCSAによって発明された。簡単で使いやすい、言葉に関係ないという特徴があります。今日はCGIを直接使ってプログラミングする人が少なくなりましたが、Apache、IIS、NgixなどのWebサーバーが主流です。
CGIはインタフェース規範を提供して、アプリケーションを提供して、普通は各種のシナリオ言語で、たとえばperl、php、pythonなどはウェブサービスを拡張して、サービスを動態化させます。
WSGI
WSGI(Web Server Gateway Interface)ウェブサービスゲートウェイインターフェース。は、ウェブサービスとウェブアプリケーションとの間のインターフェース仕様であり、PEP 333で提案されている。
wsgiはアプリケーションとウェブサービスの間を結合させ、アプリケーションは規範に従うだけで、様々なウェブサービスの配置で実行することができる。例えば、上の図では、flashk/djangoに基づいて実現されたアプリケーションは、gnicorn展開を使用しても良いし、nginx+uwsgi配置を使用しても良い。
ASGI
ASGI(Aynchronous Server Gateway Interface)非同期サーバゲートウェイインターフェース。ASGIは、非同期機能を持つPython Webサーバを目指し、フレームワークとアプリケーション間で標準インターフェースを提供する。ASGIはWSGI後方互換性の実現と複数のサーバとアプリケーションのフレームを持っています。
wsgiでは、要求応答モデルを使用して、各要求は同期して1つの応答を得ることができる。ASGIでは、要求の応答は非同期的に実現され、一般的にはwebsocketプロトコルに用いられる。asgiの内容は、非同期的な実現に関連しているので、本論文では多く紹介しない。
cgiの例
単純な概念の理解は難しいです。例に合わせて勉強します。まずCGIから始めます。
httpモジュールは簡単なファイルディレクトリサービスを提供します。
python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
このサービスは静的な展示機能だけで、私達はcgiを利用して動的機能を拡張できます。cgiスクリプト
cgi-binディレクトリを作成します。これはCGIに約束されたディレクトリ名です。そしてハロー.pyを作成します。コードは以下の通りです。
#!/usr/bin/env python
import time
import sqlite3
import os
DB_FILE = "guests.db"
def init_db():
pass #
def update_total(ts):
pass #
print('<html>')
print('<head>')
print('<meta charset="utf-8">')
print('<title>Hello Word!</title>')
print('</head>')
print('<body>')
print('<h2>Hello Python!</h2>')
if not os.path.exists(DB_FILE):
init_db()
total = update_total(time.time())
print(f'total guest: {total}!')
print('</body>')
print('</html>')
コードを簡潔にするために、db操作部分の具体的な実現を省略しました。スクリプトに実行可能な権限が必要です。ソースここです
chmod 755ハロー.py
./hello.py
<html>
<head>
<meta charset="utf-8">
<title>Hello Word!</title>
</head>
<body>
<h2>Hello Python!</h2>
total guest: 4!
</body>
</html>
http.server中のcgiサービスを起動します。
python -m http.server --cgi
後ろの--cgiパラメータに注意して、サービスにcgi-handlerを使わせます。起動後にcurlを使ってアクセスします。
curl -v http://127.0.0.1:8000/cgi-bin/hello.py
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET /cgi-bin/hello.py HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.64.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 Script output follows
< Server: SimpleHTTP/0.6 Python/3.8.5
< Date: Sun, 31 Jan 2021 13:09:29 GMT
< <html>
< <head>
< <meta charset="utf-8">
< <title>Hello Word!</title>
< </head>
< <body>
< <h2>Hello Python!</h2>
< total guest: 5! #
< </body>
< </html>
* Closing connection 0
ハロー.pyが正しく実行され、訪問者数+1が見られます。データはdbに記憶されているので、サービスの再開はまだ有効です。cgiサービスの実現
cgiの実現は主に以下のコードです。
# http.server
class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
def run_cgi(self):
import subprocess
cmdline = [scriptfile]
if self.is_python(scriptfile):
interp = sys.executable
cmdline = [interp, '-u'] + cmdline
if '=' not in query:
cmdline.append(query)
try:
nbytes = int(length)
except (TypeError, ValueError):
nbytes = 0
p = subprocess.Popen(cmdline,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env = env
)
if self.command.lower() == "post" and nbytes > 0:
data = self.rfile.read(nbytes)
# throw away additional data [see bug #427345]
while select.select([self.rfile._sock], [], [], 0)[0]:
if not self.rfile._sock.recv(1):
break
stdout, stderr = p.communicate(data)
self.wfile.write(stdout)
p.stderr.close()
p.stdout.close()
status = p.returncode
cgiの実現は:例から分かるように、http.serverはhttpサービスの提供に専念し、ap.pyは業務機能に専念し、両者はcgiで接続する。
wsgiref
wsgirefは、pythonが持参するwsgiの実現のための参考(reference)であり、主なコード構造:
ファイル
説明
handles.py
wsgi実現
headers.py
http-headerを管理する
シンプル_server.py
wsgiのhttpサービスをサポートします。
util.py&validator.py
ツールと検証
WSGIServerのコード:
class WSGIServer(HTTPServer):
"""BaseHTTPServer that implements the Python WSGI protocol"""
application = None
def server_bind(self):
"""Override server_bind to store the server name."""
HTTPServer.server_bind(self)
self.setup_environ()
def setup_environ(self): #
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PORT'] = str(self.server_port)
env['REMOTE_HOST']=''
env['CONTENT_LENGTH']=''
env['SCRIPT_NAME'] = ''
def get_app(self):
return self.application
def set_app(self,application): # application class, class
self.application = application
WSGIServerは複雑ではなく、http-serverから引き継ぎ、appplicationの注入を受けてweb-serverとwe-appplicationを接続します。接続後の動作は古い決まりで、HTTPRequest Handlerに任せて実現します。同時に、wsgiサービスはenvの動作を準備して、いくつかのwsgiの環境変数を約束しました。
class WSGIRequestHandler(BaseHTTPRequestHandler):
server_version = "WSGIServer/" + __version__
def get_environ(self):
pass
def handle(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
...
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
multithread=False,
) # handler
handler.request_handler = self
handler.run(self.server.get_app()) # application
WSGIRequest Handlerはhandlerをカバーし、httpプロトコルを処理しました。requestの後、また4つの動作をしました。
def get_environ(self):
env = self.server.base_environ.copy() # wsgi-server
env['SERVER_PROTOCOL'] = self.request_version
env['SERVER_SOFTWARE'] = self.server_version
env['REQUEST_METHOD'] = self.command
...
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0]
if self.headers.get('content-type') is None:
env['CONTENT_TYPE'] = self.headers.get_content_type()
else:
env['CONTENT_TYPE'] = self.headers['content-type']
length = self.headers.get('content-length')
if length:
env['CONTENT_LENGTH'] = length
for k, v in self.headers.items():
k=k.replace('-','_').upper(); v=v.strip()
if k in env:
continue # skip content length, type,etc.
if 'HTTP_'+k in env:
env['HTTP_'+k] += ','+v # comma-separate multiple headers
else:
env['HTTP_'+k] = v
return env
Server Handlerオブジェクトの作成、入出力/エラー、および環境変数情報を受け付けます。
class ServerHandler(BaseHandler):
def __init__(self,stdin,stdout,stderr,environ,
multithread=True, multiprocess=False
):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.base_env = environ
self.wsgi_multithread = multithread
self.wsgi_multiprocess = multiprocess
...
ポイントはServerhandlerのrun関数です。
class BaseHandler:
def run(self, application):
"""Invoke the application"""
# Note to self: don't move the close()! Asynchronous servers shouldn't
# call close() from finish_response(), so if you close() anywhere but
# the double-error branch here, you'll break asynchronous servers by
# prematurely closing. Async servers must return from 'run()' without
# closing if there might still be output to iterate over.
...
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
...
キーの3つのステップ:
def setup_environ(self):
"""Set up the environment for one request"""
env = self.environ = self.os_environ.copy()
self.add_cgi_vars() # self.environ.update(self.base_env)
env['wsgi.input'] = self.get_stdin() # stdout
env['wsgi.errors'] = self.get_stderr()
env['wsgi.version'] = self.wsgi_version
env['wsgi.run_once'] = self.wsgi_run_once
env['wsgi.url_scheme'] = self.get_scheme()
env['wsgi.multithread'] = self.wsgi_multithread
env['wsgi.multiprocess'] = self.wsgi_multiprocess
if self.wsgi_file_wrapper is not None:
env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
if self.origin_server and self.server_software:
env.setdefault('SERVER_SOFTWARE',self.server_software)
envの処理手順は、3ステップ:1)serverの実行情報2を付加して要求されたhttpヘッダ(プロトコル情報)3)に追加要求されたストリーム情報として理解できる。envは、httpが要求するすべてのコンテキスト環境という言い方に変えられます。アプリはまた、フィードバック関数start_を受信します。レスポンスは主にhttpプロトコルの仕様に従い、レスポンス状態とレスポンスを生成します。header:
def start_response(self, status, headers,exc_info=None):
"""'start_response()' callable as specified by PEP 3333"""
self.status = status
self.headers = self.headers_class(headers)
status = self._convert_string_type(status, "Status")
assert len(status)>=4,"Status must be at least 4 characters"
assert status[:3].isdigit(), "Status message must begin w/3-digit code"
assert status[3]==" ", "Status message must have a space after code"
return self.write
アプリケーションの要求に対する処理:
def demo_app(environ,start_response):
from io import StringIO
stdout = StringIO()
print("Hello world!", file=stdout)
print(file=stdout)
# http
h = sorted(environ.items())
for k,v in h:
print(k,'=',repr(v), file=stdout)
# http_status, response_headers
start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
# response_body
return [stdout.getvalue().encode("utf-8")]
レスポンスはまだServer Handlerによって書き込まれています。
def finish_response(self):
if not self.result_is_file() or not self.sendfile():
for data in self.result:
self.write(data)
self.finish_content()
以下の命令を使ってこの流れをテストできます。
python -m wsgiref.simple_server
Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [31/Jan/2021 21:43:05] "GET /xyz?abc HTTP/1.1" 200 3338
wsgiまとめ簡単で簡単な小さい結び目のwsgiの実現。http要求の処理フローweb-browser<->web-server<->wsgi<->web-appicationでは、階層的な思想が具現されており、それぞれの層が異なることをする:
テクニック
wsgirefコードの中にも様々な小さい技術があります。勉強したらコードをもっとpythonicにします。
環境変数はこのように設定されています。
def setup_environ(self):
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
...
前は大体こう書きました。
def setup_environ(self):
self.base_environ = {}
self.base_environ['SERVER_NAME'] = self.server_name
self.base_environ['GATEWAY_INTERFACE'] = 'CGI/1.1'
対照的に、前の書き方はもっと簡潔であることが分かります。例えばストリームの継続書込み:
def _write(self,data):
result = self.stdout.write(data)
if result is None or result == len(data):
return
from warnings import warn
warn("SimpleHandler.stdout.write() should not do partial writes",
DeprecationWarning)
while True:
data = data[result:] # ,
if not data:
break
result = self.stdout.write(data)
例えばheaderの処理は、配列を辞書として使っています。
class Headers:
"""Manage a collection of HTTP response headers"""
def __init__(self, headers=None):
headers = headers if headers is not None else []
self._headers = headers #
def __setitem__(self, name, val):
"""Set the value of a header."""
del self[name]
self._headers.append(
(self._convert_string_type(name), self._convert_string_type(val)))
....
def __getitem__(self,name):
"""Get the first header value for 'name'
Return None if the header is missing instead of raising an exception.
Note that if the header appeared multiple times, the first exactly which
occurrence gets returned is undefined. Use getall() to get all
the values matching a header field name.
"""
return self.get(name)
def get(self,name,default=None):
"""Get the first header value for 'name', or return 'default'"""
name = self._convert_string_type(name.lower())
for k,v in self._headers:
if k.lower()==name:
return v
return default
このようなContent-Type: application/javascript; charset=utf-8
の値は、以下のように使用されてもよい。
if self.headers.get('content-type') is None:
env['CONTENT_TYPE'] = self.headers.get_content_type()
else:
env['CONTENT_TYPE'] = self.headers['content-type']
辞書ではなく行列を使うのはなぜですか?私の推測では、headerの特性はデータの読み操作が多いからです。以上がpython wsgirefソースの解析の詳細です。python wsgirefソースに関する詳細については、他の関連記事に注目してください。