WSGI的全稱是Web Server Gateway Interface了罪,翻譯過來就是Web服務(wù)器網(wǎng)關(guān)接口。具體的來說聪全,WSGI是一個規(guī)范泊藕,定義了Web服務(wù)器如何與Python應(yīng)用程序進行交互,使得使用Python寫的Web應(yīng)用程序可以和Web服務(wù)器對接起來。WSGI一開始是在PEP-0333中定義的娃圆,最新版本是在Python的PEP-3333定義的玫锋。
WSGI是什么
WSGI的全稱是Web Server Gateway Interface,翻譯過來就是Web服務(wù)器網(wǎng)關(guān)接口讼呢。具體的來說撩鹿,WSGI是一個規(guī)范,定義了Web服務(wù)器如何與Python應(yīng)用程序進行交互悦屏,使得使用Python寫的Web應(yīng)用程序可以和Web服務(wù)器對接起來节沦。WSGI一開始是在PEP-0333中定義的,最新版本是在Python的PEP-3333定義的础爬。
對于初學(xué)者來說甫贯,上面那段就是廢話,說了跟沒說一樣看蚜。本文的主要內(nèi)容就是說清楚叫搁,WSGI到底是如何工作的。
為什么需要WSGI這個規(guī)范
在Web部署的方案上供炎,有一個方案是目前應(yīng)用最廣泛的:
首先渴逻,部署一個Web服務(wù)器專門用來處理HTTP協(xié)議層面相關(guān)的事情,比如如何在一個物理機上提供多個不同的Web服務(wù)(單IP多域名音诫,單IP多端口等)這種事情惨奕。
然后,部署一個用各種語言編寫(Java, PHP, Python, Ruby等)的應(yīng)用程序纽竣,這個應(yīng)用程序會從Web服務(wù)器上接收客戶端的請求墓贿,處理完成后,再返回響應(yīng)給Web服務(wù)器蜓氨,最后由Web服務(wù)器返回給客戶端。
那么队伟,要采用這種方案穴吹,Web服務(wù)器和應(yīng)用程序之間就要知道如何進行交互。為了定義Web服務(wù)器和應(yīng)用程序之間的交互過程嗜侮,就形成了很多不同的規(guī)范港令。這種規(guī)范里最早的一個是CGI][3,1993年開發(fā)的锈颗。后來又出現(xiàn)了很多這樣的規(guī)范顷霹。比如改進CGI性能的FasgCGI,Java專用的Servlet規(guī)范击吱,還有Python專用的WSGI規(guī)范等淋淀。提出這些規(guī)范的目的就是為了定義統(tǒng)一的標(biāo)準(zhǔn),提升程序的可移植性覆醇。在WSGI規(guī)范的最開始的PEP-333中一開始就描述了為什么需要WSGI規(guī)范朵纷。
WSGI如何工作
從上文可以知道炭臭,WSGI相當(dāng)于是Web服務(wù)器和Python應(yīng)用程序之間的橋梁。那么這個橋梁是如何工作的呢袍辞?首先鞋仍,我們明確橋梁的作用,WSGI存在的目的有兩個:
讓W(xué)eb服務(wù)器知道如何調(diào)用Python應(yīng)用程序搅吁,并且把用戶的請求告訴應(yīng)用程序威创。
讓Python應(yīng)用程序知道用戶的具體請求是什么,以及如何返回結(jié)果給Web服務(wù)器谎懦。
WSGI中的角色
在WSGI中定義了兩個角色那婉,Web服務(wù)器端稱為server或者gateway,應(yīng)用程序端稱為application或者framework(因為WSGI的應(yīng)用程序端的規(guī)范一般都是由具體的框架來實現(xiàn)的)党瓮。我們下面統(tǒng)一使用server和application這兩個術(shù)語详炬。
server端會先收到用戶的請求,然后會根據(jù)規(guī)范的要求調(diào)用application端寞奸,如下圖所示:
[圖片上傳失敗...(image-1bd5a9-1541121120404)]
調(diào)用的結(jié)果會被封裝成HTTP響應(yīng)后再發(fā)送給客戶端呛谜。
server如何調(diào)用application
首先,每個application的入口只有一個枪萄,也就是所有的客戶端請求都同一個入口進入到應(yīng)用程序隐岛。
接下來,server端需要知道去哪里找application的入口瓷翻。這個需要在server端指定一個Python模塊聚凹,也就是Python應(yīng)用中的一個文件,并且這個模塊中需要包含一個名稱為application的可調(diào)用對象(函數(shù)和類都可以)齐帚,這個application對象就是這個應(yīng)用程序的唯一入口了妒牙。WSGI還定義了application對象的形式:
def simple_app(environ, start_response):
pass
上面代碼中的environ
和start_response
就是server端調(diào)用application對象時傳遞的兩個參數(shù)。
我們來看具體的例子对妄。假設(shè)我們的應(yīng)用程序的入口文件是/var/www/index.py
湘今,那么我們就需要在server端配置好這個路徑(如何配置取決于server端的實現(xiàn)),然后在index.py
中的代碼如下所示:
使用標(biāo)準(zhǔn)庫(這個只是demo)
import wsgiref
application = wsgiref.simple_server.demo_app
使用web.py框架
import web
urls = (
'/.*', 'hello',
)
class hello(object):
def GET(self):
return "Hello, world."
application = web.application(urls, globals()).wsgifunc()
你可以看到剪菱,文件中都需要有一個application對象摩瞎,server端會找到這個文件,然后調(diào)用這個對象孝常。所以支持WSGI的Python框架最終都會有這么一個application對象旗们,不過框架的使用者不需要關(guān)心這個application對象內(nèi)部是如何工作的,只需要關(guān)心路由定義构灸、請求處理等具體的業(yè)務(wù)邏輯上渴。
因為application對象是唯一的入口,所以不管客戶端請求的路徑和數(shù)據(jù)是什么,server都是調(diào)用這個application對象驰贷,具體的客戶端請求的處理有application對象完成盛嘿。
application對象需要做什么
上面已經(jīng)提到了,application對象需要是一個可調(diào)用對象括袒,而且其定義需要滿足如下形式:
def simple_app(environ, start_response):
pass
當(dāng)server按照WSGI的規(guī)范調(diào)用了application之后次兆,application就可以開始處理客戶端的請求了,處理請求之后锹锰,application對象需要返回處理結(jié)果給server端芥炭。處理請求和返回結(jié)果這兩個事情,都和server調(diào)用application對象時傳遞的兩個參數(shù)有關(guān)恃慧。
environ參數(shù)
environ參數(shù)是一個Python的字典园蝠,里面存放了所有和客戶端相關(guān)的信息,這樣application對象就能知道客戶端請求的資源是什么痢士,請求中帶了什么數(shù)據(jù)等彪薛。environ字典包含了一些CGI規(guī)范要求的數(shù)據(jù),以及WSGI規(guī)范新增的數(shù)據(jù)怠蹂,還可能包含一些操作系統(tǒng)的環(huán)境變量以及Web服務(wù)器相關(guān)的環(huán)境變量善延。我們來看一些environ中常用的成員:
首先是CGI規(guī)范中要求的變量:
REQUEST_METHOD: 請求方法,是個字符串城侧,'GET', 'POST'等
SCRIPT_NAME: HTTP請求的path中的用于查找到application對象的部分易遣,比如Web服務(wù)器可以根據(jù)path的一部分來決定請求由哪個virtual host處理
PATH_INFO: HTTP請求的path中剩余的部分,也就是application要處理的部分
QUERY_STRING: HTTP請求中的查詢字符串嫌佑,URL中?后面的內(nèi)容
CONTENT_TYPE: HTTP headers中的content-type內(nèi)容
CONTENT_LENGTH: HTTP headers中的content-length內(nèi)容
SERVER_NAME和SERVER_PORT: 服務(wù)器名和端口豆茫,這兩個值和前面的SCRIPT_NAME, PATH_INFO拼起來可以得到完整的URL路徑
SERVER_PROTOCOL: HTTP協(xié)議版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP請求中的headers對應(yīng)屋摇。
WSGI規(guī)范中還要求environ包含下列成員:
wsgi.version:表示W(wǎng)SGI版本揩魂,一個元組(1, 0),表示版本1.0
wsgi.url_scheme:http或者h(yuǎn)ttps
wsgi.input:一個類文件的輸入流摊册,application可以通過這個獲取HTTP request body
wsgi.errors:一個輸出流肤京,當(dāng)應(yīng)用程序出錯時,可以將錯誤信息寫入這里
wsgi.multithread:當(dāng)application對象可能被多個線程同時調(diào)用時茅特,這個值需要為True
wsgi.multiprocess:當(dāng)application對象可能被多個進程同時調(diào)用時,這個值需要為True
wsgi.run_once:當(dāng)server期望application對象在進程的生命周期內(nèi)只被調(diào)用一次時棋枕,該值為True
上面列出的這些內(nèi)容已經(jīng)包括了客戶端請求的所有數(shù)據(jù)白修,足夠application對象處理客戶端請求了。
start_resposne參數(shù)
start_response是一個可調(diào)用對象重斑,接收兩個必選參數(shù)和一個可選參數(shù):
status: 一個字符串兵睛,表示HTTP響應(yīng)狀態(tài)字符串
response_headers: 一個列表,包含有如下形式的元組:(header_name, header_value),用來表示HTTP響應(yīng)的headers
exc_info(可選): 用于出錯時祖很,server需要返回給瀏覽器的信息
當(dāng)application對象根據(jù)environ參數(shù)的內(nèi)容執(zhí)行完業(yè)務(wù)邏輯后笛丙,就需要返回結(jié)果給server端。我們知道HTTP的響應(yīng)需要包含status假颇,headers和body胚鸯,所以在application對象將body作為返回值return之前,需要先調(diào)用start_response()
笨鸡,將status和headers的內(nèi)容返回給server姜钳,這同時也是告訴server,application對象要開始返回body了形耗。
application對象的返回值
application對象的返回值用于為HTTP響應(yīng)提供body哥桥,如果沒有body,那么可以返回None激涤。如果有body的化拟糕,那么需要返回一個可迭代的對象。server端通過遍歷這個可迭代對象可以獲得body的全部內(nèi)容倦踢。
application demo
PEP-3333中有一個application的實現(xiàn)demo送滞,我把它再簡化之后如下:
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['hello, world']
可以將這段代碼和前面的說明對照起來理解。
再談server如何調(diào)用application
前面已經(jīng)知道server如何定位到application的入口了硼一,也知道了application的入口的形式以及application對象內(nèi)部需要完成的工作累澡。那么,我們還需要再說一下般贼,environ
和start_response()
是需要在server端的生成和定義的愧哟,其中關(guān)于start_response()
的部分在規(guī)范中也有明確的要求。這部分內(nèi)容太長了哼蛆,不適合放在本文中蕊梧,有興趣的讀者可以去看下PEP-3333,里面有一段server端的demo實現(xiàn)腮介。
WSGI中間件
WSGI Middleware(中間件)也是WSGI規(guī)范的一部分肥矢。上一章我們已經(jīng)說明了WSGI的兩個角色:server和application。那么middleware是一種運行在server和application中間的應(yīng)用(一般都是Python應(yīng)用)叠洗。middleware同時具備server和application角色甘改,對于server來說,它是一個application灭抑;對于application來說十艾,它是一個server。middleware并不修改server端和application端的規(guī)范腾节,只是同時實現(xiàn)了這兩個角色的功能而已忘嫉。
我們可以通過下圖來說明middleware是如何工作的:
上圖中最上面的三個彩色框表示角色荤牍,中間的白色框表示操作,操作的發(fā)生順序按照1 ~ 5進行了排序庆冕,我們直接對著上圖來說明middleware是如何工作的:
Server收到客戶端的HTTP請求后康吵,生成了
environ_s
,并且已經(jīng)定義了start_response_s
访递。Server調(diào)用Middleware的application對象晦嵌,傳遞的參數(shù)是
environ_s
和start_response_s
。Middleware會根據(jù)
environ
執(zhí)行業(yè)務(wù)邏輯力九,生成environ_m
耍铜,并且已經(jīng)定義了start_response_m
。Middleware決定調(diào)用Application的application對象跌前,傳遞參數(shù)是
environ_m
和start_response_m
棕兼。Application的application對象處理完成后,會調(diào)用start_response_m
并且返回結(jié)果給Middleware抵乓,存放在result_m
中伴挚。Middleware處理
result_m
,然后生成result_s
灾炭,接著調(diào)用start_response_s
茎芋,并返回結(jié)果result_s
給Server端。Server端獲取到result_s后就可以發(fā)送結(jié)果給客戶端了蜈出。
從上面的流程可以看出middleware應(yīng)用的幾個特點:
Server認(rèn)為middleware是一個application田弥。
Application認(rèn)為middleware是一個server。
Middleware可以有多層铡原。
因為Middleware能過處理所有經(jīng)過的request和response偷厦,所以要做什么都可以,沒有限制燕刻。比如可以檢查request是否有非法內(nèi)容只泼,檢查response是否有非法內(nèi)容,為request加上特定的HTTP header等卵洗,這些都是可以的请唱。
WSGI的實現(xiàn)和部署
要使用WSGI,需要分別實現(xiàn)server角色和application角色过蹂。
Application端的實現(xiàn)一般是由Python的各種框架來實現(xiàn)的十绑,比如Django, web.py等,一般開發(fā)者不需要關(guān)心WSGI的實現(xiàn)酷勺,框架會會提供接口讓開發(fā)者獲取HTTP請求的內(nèi)容以及發(fā)送HTTP響應(yīng)孽惰。
Server端的實現(xiàn)會比較復(fù)雜一點,這個主要是因為軟件架構(gòu)的原因鸥印。一般常用的Web服務(wù)器勋功,如Apache和nginx,都不會內(nèi)置WSGI的支持库说,而是通過擴展來完成狂鞋。比如Apache服務(wù)器,會通過擴展模塊mod_wsgi來支持WSGI潜的。Apache和mod_wsgi之間通過程序內(nèi)部接口傳遞信息骚揍,mod_wsgi會實現(xiàn)WSGI的server端、進程管理以及對application的調(diào)用啰挪。Nginx上一般是用proxy的方式信不,用nginx的協(xié)議將請求封裝好,發(fā)送給應(yīng)用服務(wù)器亡呵,比如uWSGI抽活,應(yīng)用服務(wù)器會實現(xiàn)WSGI的服務(wù)端、進程管理以及對application的調(diào)用锰什。