介紹
要很好地理解下面的程式碼,最好有一定的 socket 程式設計基礎,瞭解 socket 的基本概念和流程。
wsgiref 是 PEP 333 定義的 wsgi 規範的範例實現,裡面的功能包括了:
- 操作 wsgi 的環境變數
- 應答頭部的處理
- 實現簡單的 HTTP server
- 簡單的對程式端和伺服器端校驗函式
我們先看一個簡單的程式碼例項,然後跟著例子去理解原始碼:
app.py
1 2 3 4 5 6 7 8 9 10 |
# pep333 定義的程式端可呼叫物件 def hello_world_app(environ, start_response): status = '200 OK' # HTTP Status headers = [('Content-type', 'text/plain')] # HTTP Headers start_response(status, headers) # The returned object is going to be printed return ["Hello World"] |
server.py
1 2 3 4 5 6 7 8 9 |
from app import hello_world_app from wsgiref.simple_server import make_server httpd = make_server('', 8000, hello_world_app) print "Serving on port 8000..." # Serve until process is killed httpd.serve_forever() |
然後執行 python server.py
啟動 sever,用 curl 傳送一個請求 curl -i http://localhost:8000/
,會有以下輸出:
1 2 3 4 5 6 7 8 |
HTTP/1.0 200 OK Date: Sat, 08 Nov 2014 09:08:05 GMT Server: WSGIServer/0.1 Python/2.7.3 Content-type: text/plain Content-Length: 12 Hello World |
server 的終端會有一條記錄:
1 2 3 |
Serving on port 8000... localhost - - [08/Nov/2014 09:08:05] "GET / HTTP/1.1" 200 12 |
如何使用就講到這裡,下面就開始原始碼之旅吧!
原始碼分析
你可以使用 python -c 'import wsgiref; help(wsgiref)'
檢視 wsgiref 庫的路徑和簡介等資訊,wsgiref 資料夾的結構如下:
1 2 3 4 5 6 7 8 |
wsgiref |-- handlers.py # 核心程式碼,負責 wsgi 程式的處理 |-- headers.py # 頭部處理的程式碼 |-- __init__.py # |-- simple_server.py # 簡單的 wsgi HTTP 伺服器實現 |-- util.py # 幫助函式 `-- validate.py # wsgi 格式檢查和校驗 |
主要的程式碼結構如下圖所示:
simple_server.py
我們先看一下 make_server
是怎麼啟動一個 wsgi 伺服器的:
1 2 3 4 5 |
def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler): server = server_class((host, port), handler_class) server.set_app(app) return server |
這個函式做的事情就是:監聽在本地的埠上,接受來自客戶端的請求,通過 WSGIServer 和 WSGIRequestHandler 處理後,把請求交給程式的的可呼叫物件 app,然後返回 app 的結果給客戶端。
這裡有兩個重要的類:WSGIServer 和 WSGIRequestHandler。下面分別看一下它們的程式碼和執行的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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): self.application = application |
WSGIServer 在原來的 HTTPServer 上面封裝了一層,在原來的 HTTPServer 的基礎上又額外做了下面的事情:
- 覆寫原來的 server_bind 函式,新增初始化 environ 變數的動作
- 新增了處理滿足 wsgi 的 app 函式:set_app 和 get_app
然後看另外一個類 WSGIRequestHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
class WSGIRequestHandler(BaseHTTPRequestHandler): server_version = "WSGIServer/" + __version__ def get_environ(self): env = self.server.base_environ.copy() env['SERVER_PROTOCOL'] = self.request_version env['REQUEST_METHOD'] = self.command if '?' in self.path: path,query = self.path.split('?',1) else: path,query = self.path,'' env['PATH_INFO'] = urllib.unquote(path) env['QUERY_STRING'] = query host = self.address_string() if host != self.client_address[0]: env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] if self.headers.typeheader is None: env['CONTENT_TYPE'] = self.headers.type else: env['CONTENT_TYPE'] = self.headers.typeheader length = self.headers.getheader('content-length') if length: env['CONTENT_LENGTH'] = length for h in self.headers.headers: k,v = h.split(':',1) 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 def get_stderr(self): return sys.stderr def handle(self): """Handle a single HTTP request""" self.raw_requestline = self.rfile.readline() 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() ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app()) |
這個類從名字就能知道它的功能——處理客戶端的 HTTP 請求,它也是在原來處理 http 請求的BaseHTTPRequestHandler 類上新增了 wsgi 規範相關的內容。
- get_environ: 解析 environ 變數
- handle: 處理請求,把封裝的環境變數交給 ServerHandler,然後由 ServerHandler 呼叫 wsgi app,ServerHandler 類會在下面介紹。
handler.py
這個檔案主要是 wsgi server 的處理過程,定義 start_response、呼叫 wsgi app 、處理 content-length 等等。
可以參考這篇文章裡的 wsgi server 的簡單實現。
一條 HTTP 請求的旅程
伺服器端啟動服務,等到客戶端輸入 curl -i http://localhost:8000/
命令,摁下Enter鍵,看到終端上的輸出,整個過程中,wsgi 的伺服器端發生了什麼呢?
- 伺服器程式建立 socket,並監聽在特定的埠,等待客戶端的連線
- 客戶端傳送 http 請求
- socket server 讀取請求的資料,交給 http server
- http server 根據 http 的規範解析請求,然後把請求交給 WSGIServer
- WSGIServer 把客戶端的資訊存放在 environ 變數裡,然後交給繫結的 handler 處理請求
- HTTPHandler 解析請求,把 method、path 等放在 environ,然後 WSGIRequestHandler 把伺服器端的資訊也放到 environ 裡
- WSGIRequestHandler 呼叫繫結的 wsgi ServerHandler,把上面包含了伺服器資訊,客戶端資訊,本次請求資訊得 environ 傳遞過去
- wsgi ServerHandler 呼叫註冊的 wsgi app,把 environ 和 start_response 傳遞過去
- wsgi app 將reponse header、status、body 回傳給 wsgi handler
- 然後 handler 逐層傳遞,最後把這些資訊通過 socket 傳送到客戶端
- 客戶端的程式接到應答,解析應答,並把結果列印出來。