WSGI初探

[size=x-large]wsgi初探[/size]
[size=large]前言[/size]

本文不涉及WSGI的具體協議的介紹,也不會有協議完整的實現,甚至描述中還會摻雜着本人自己對於WSGI的見解。所有的WSGI官方定義請看[url="http://www.python.org/dev/peps/pep-3333/"]http://www.python.org/dev/peps/pep-3333/[/url]。


[size=large]WSGI是什麼?[/size]

WSGI的官方定義是,the Python Web Server Gateway Interface。從名字就可以看出來,這東西是一個Gateway,也就是網關。網關的作用就是在協議之間進行轉換。


也就是說,WSGI就像是一座橋樑,一邊連着web服務器,另一邊連着用戶的應用。但是呢,這個橋的功能很弱,有時候還需要別的橋來幫忙才能進行處理。


下面對本文出現的一些名詞做定義。 [b]wsgi app[/b] ,又稱 [b]應用[/b] ,就是一個WSGI application。 [b]wsgi container[/b] ,又稱 [b]容器[/b] ,雖然這個部分常常被稱爲handler,不過我個人認爲handler容易和app混淆,所以我稱之爲容器。 [b]wsgi_middleware[/b] ,又稱 [b]中間件[/b] 。一種特殊類型的程序,專門負責在容器和應用之間幹壞事的。


一圖勝千言,直接來一個我自己理解的WSGI架構圖吧。

[img]http://farm5.static.flickr.com/4153/5132284950_a129982b88.jpg[/img]

可以看出,服務器,容器和應用之間存在着十分糾結的關係。下面就要把這些糾結的關係理清楚。


[size=large]WSGI應用[/size]


WSGI應用其實就是一個callable的對象。舉一個最簡單的例子,假設存在如下的一個應用:



def application(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(12)]
write = start_response(status, response_headers)
write('Hello ')
return [output]



這個WSGI應用簡單的可以用簡陋來形容,但是他的確是一個功能完整的WSGI應用。只不過給人留下了太多的疑點,environ是什麼?start_response是什麼?爲什麼可以同時用write和return來返回內容?


對於這些疑問,不妨自己猜測一下他的作用。聯想到CGI,那麼environ可能就是一系列的環境變量,用來表示HTTP請求的信息,比如說method之類的。start_response,可能是接受HTTP response頭信息,然後返回一個write函數,這個write函數可以把HTTP response的body返回給客戶端。return自然是將HTTP response的body信息返回。不過這裏的write和函數返回有什麼區別?會不會是其實外圍默認調用write對應用返回值進行處理?而且爲什麼應用的返回值是一個列表呢?說明肯定存在一個對應用執行結果的迭代輸出過程。難道說他隱含的支持iterator或者generator嗎?


等等,應用執行結果?一個應用既然是一個函數,說明肯定有一個對象去執行它,並且可以猜到,這個對象把environ和start_response傳給應用,將應用的返回結果輸出給客戶端。那麼這個對象是什麼呢?自然就是WSGI容器了。


[size=large]WSGI容器[/size]


先說說WSGI容器的來源,其實這是我自己編造出來的一個概念。來源就是JavaServlet容器。我個人理解兩者有相似的地方,就順手拿過來用了。


WSGI容器的作用,就是構建一個讓WSGI應用成功執行的環境。成功執行,意味着需要傳入正確的參數,以及正確處理返回的結果,還得把結果返回給客戶端。


所以,WSGI容器的工作流程大致就是,用webserver規定的通信方式,能從webserver獲得正確的request信息,封裝好,傳給WSGI應用執行,正確的返回response。


一般來說,WSGI容器必須依附於現有的webserver的技術才能實現,比如說CGI,FastCGI,或者是embed的模式。


下面利用CGI的方式編寫一個最簡單的WSGI容器。關於WSGI容器的協議官方文檔並沒有具體的說如何實現,只是介紹了一些需要約束的東西。具體內容看PEP3333中的協議。



#!/usr/bin/python
#encoding:utf8

import cgi
import cgitb
import sys
import os

#Make the environ argument
environ = {}
environ['REQUEST_METHOD'] = os.environ['REQUEST_METHOD']
environ['SCRIPT_NAME'] = os.environ['SCRIPT_NAME']
environ['PATH_INFO'] = os.environ['PATH_INFO']
environ['QUERY_STRING'] = os.environ['QUERY_STRING']
environ['CONTENT_TYPE'] = os.environ['CONTENT_TYPE']
environ['CONTENT_LENGTH'] = os.environ['CONTENT_LENGTH']
environ['SERVER_NAME'] = os.environ['SERVER_NAME']
environ['SERVER_PORT'] = os.environ['SERVER_PORT']
environ['SERVER_PROTOCOL'] = os.environ['SERVER_PROTOCOL']
environ['wsgi.version'] = (1, 0)
environ['wsgi.url_scheme'] = 'http'
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True


#make the start_response argument
#注意,WSGI協議規定,如果沒有body內容,是不能返回http response頭信息的。
sent_header = False
res_status = None
res_headers = None

def write(body):
global sent_header
if sent_header:
sys.stdout.write(body)
else:
print res_status
for k, v in res_headers:
print k + ': ' + v
print
sys.stdout.write(body)
sent_header = True

def start_response(status, response_headers):
global res_status
global res_headers
res_status = status
res_headers = response_headers
return write

#here is the application
def application(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(12)]
write = start_response(status, response_headers)
write('Hello ')
return [output]

#here run the application
result = application(environ, start_response)
for value in result:
write(value)



看吧。其實實現一個WSGI容器也不難。


不過我從WSGI容器的設計中可以看出WSGI的應用設計上面存在着一個重大的問題就是:爲什麼要提供兩種方式返回數據?明明只有一個write函數,卻既可以在application裏面調用,又可以在容器中傳輸應用的返回值來調用。如果說讓我來設計的話,直接把start_response給去掉了。就用application(environ)這個接口。傳一個方法,然後返回值就是status, response_headers和一個字符串的列表。實際傳輸的方法全部隱藏了。用戶只需要從environ中讀取數據處理就行了。。


可喜的是,搜了一下貌似web3的標準裏面應用的設計和我的想法類似。希望web3協議能早日普及。


[size=large]Middleware中間件[/size]


中間件是一類特殊的程序,可以在容器和應用之間幹一些壞事。。其實熟悉python的decorator的人就會發現,這和decoraotr沒什麼區別。


下面來實現一個route的簡單middleware。


class Router(object):
def __init__(self):
self.path_info = {}
def route(self, environ, start_response):
application = self.path_info[environ['PATH_INFO']]
return application(environ, start_response)
def __call__(self, path):
def wrapper(application):
self.path_info[path] = application
return wrapper



這就是一個很簡單的路由功能的middleware。將上面那段wsgi容器的代碼裏面的應用修改成如下:



router = Router()

#here is the application
@router('/hello')
def hello(environ, start_response):
status = '200 OK'
output = 'Hello'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
write = start_response(status, response_headers)
return [output]

@router('/world')
def world(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
write = start_response(status, response_headers)
return [output]
#here run the application
result = router.route(environ, start_response)
for value in result:
write(value)



這樣,容器就會自動的根據訪問的地址找到對應的app執行了。


[size=large]延伸[/size]


寫着寫着,怎麼越來越像一個框架了?看來Python開發框架真是簡單。。


其實從另外一個角度去考慮。如果把application當作是一個運算單元。利用middleware調控IO和運算資源,那麼利用WSGI組成一個分佈式的系統。


好吧,全文完。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章