WSGI 簡介
wsgi代碼源碼詳解>>點我
介紹
要很好地理解下面的代碼,最好有一定的 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/
命令,摁下回車鍵,看到終端上的輸出,整個過程中,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 發送到客戶端
- 客戶端的程序接到應答,解析應答,並把結果打印出來。
背景
Python Web 開發中,服務端程序可以分為兩個部分,一是服務器程序,二是應用程序。前者負責把客戶端請求接收,整理,后者負責具體的邏輯處理。為了方便應用程序的開發,我們把常用的功能封裝起來,成為各種Web開發框架,例如 Django, Flask, Tornado。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。這樣,服務器程序就需要為不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。
這時候,標准化就變得尤為重要。我們可以設立一個標准,只要服務器程序支持這個標准,框架也支持這個標准,那么他們就可以配合使用。一旦標准確定,雙方各自實現。這樣,服務器可以支持更多支持標准的框架,框架也可以使用更多支持標准的服務器。
Python Web開發中,這個標准就是 The Web Server Gateway Interface, 即 WSGI. 這個標准在PEP 333中描述,后來,為了支持 Python 3.x, 並且修正一些問題,新的版本在PEP 3333中描述。
WSGI 是什么
WSGI 是服務器程序與應用程序的一個約定,它規定了雙方各自需要實現什么接口,提供什么功能,以便二者能夠配合使用。
WSGI 不能規定的太復雜,否則對已有的服務器來說,實現起來會困難,不利於WSGI的普及。同時WSGI也不能規定的太多,例如cookie處理就沒有在WSGI中規定,這是為了給框架最大的靈活性。要知道WSGI最終的目的是為了方便服務器與應用程序配合使用,而不是成為一個Web框架的標准。
另一方面,WSGI需要使得middleware(是中間件么?)易於實現。middleware處於服務器程序與應用程序之間,對服務器程序來說,它相當於應用程序,對應用程序來說,它相當於服務器程序。這樣,對用戶請求的處理,可以變成多個 middleware 疊加在一起,每個middleware實現不同的功能。請求從服務器來的時候,依次通過middleware,響應從應用程序返回的時候,反向通過層層middleware。我們可以方便地添加,替換middleware,以便對用戶請求作出不同的處理。
WSGI 內容概要
WSGI主要是對應用程序與服務器端的一些規定,所以,它的主要內容就分為兩個部分。
應用程序
WSGI規定:
1. 應用程序需要是一個可調用的對象
在Python中:
- 可以是函數
- 可以是一個實例,它的類實現了
__call__
方法 - 可以是一個類,這時候,用這個類生成實例的過程就相當於調用這個類
同時,WSGI規定:
2. 可調用對象接收兩個參數
這樣,如果這個對象是函數的話,它看起來要是這個樣子:
# callable function
def application(environ, start_response):
pass
如果這個對象是一個類的話,它看起來是這個樣子:
# callable class
class Application:
def __init__(self, environ, start_response):
pass
如果這個對象是一個類的實例,那么,這個類看起來是這個樣子:
# callable object
class ApplicationObj:
def __call__(self, environ, start_response):
pass
最后,WSGI還規定:
3.可調用對象要返回一個值,這個值是可迭代的。
middleware
另外,有些功能可能介於服務器程序和應用程序之間,例如,服務器拿到了客戶端請求的URL, 不同的URL需要交由不同的函數處理,這個功能叫做 URL Routing,這個功能就可以放在二者中間實現,這個中間層就是 middleware。
middleware對服務器程序和應用是透明的,也就是說,服務器程序以為它就是應用程序,而應用程序以為它就是服務器。這就告訴我們,middleware需要把自己偽裝成一個服務器,接受應用程序,調用它,同時middleware還需要把自己偽裝成一個應用程序,傳給服務器程序。
其實無論是服務器程序,middleware 還是應用程序,都在服務端,為客戶端提供服務,之所以把他們抽象成不同層,就是為了控制復雜度,使得每一次都不太復雜,各司其職。