本文譯自:https://www.python.org/dev/peps/pep-0333
前一部分內(nèi)容請(qǐng)查看:http://10111000.com/2017/12/22/PEP333_1/
tips: 以下, 服務(wù)端代指服務(wù)端和網(wǎng)關(guān), 框架代指應(yīng)用端和框架端
規(guī)范細(xì)節(jié)
應(yīng)用對(duì)象必須接收兩個(gè)位置參數(shù)。為了更好的說(shuō)明,我們把兩個(gè)變量分別命名為environ和start_response,但是這個(gè)規(guī)范里并不要求和變量名完全一致。服務(wù)器端必須傳入相應(yīng)的位置參數(shù)(不是字典參數(shù))去調(diào)用應(yīng)用對(duì)象。(如上所述,我們可以以這種方式進(jìn)行調(diào)用:result = application(environ, start_response)。)
environ是一個(gè)字典對(duì)象,包含CGI風(fēng)格的環(huán)境變量,這個(gè)對(duì)象必須是一個(gè)內(nèi)置的python字典(不是一個(gè)子類,用戶自定義的字典或其他擁有類似字典操作的對(duì)象),且可以被框架端任意修改成它想要的形式。這個(gè)environ也必須包含某些WSGI所需要的變量(稍后會(huì)加以詳述),也可能包含服務(wù)器的某些擴(kuò)展變量,按照慣例,如何命名將會(huì)在下文提及。
start_response是一個(gè)可接受兩個(gè)位置參數(shù),一個(gè)可選參數(shù)的可調(diào)用對(duì)象。同樣為了說(shuō)明,這個(gè)三個(gè)參數(shù)分別命名為status, response_headers 和 exc_info,它們也可以有不同的命名,框架端必須傳入對(duì)應(yīng)的位置參數(shù)去調(diào)用start_response方法。(例如:start_response(status, response_headers))
status參數(shù)是一個(gè)形如“999 message here”的狀態(tài)字符串,response_headers是由HTTP響應(yīng)頭形成的元組(header_name, header_value)在一起組成的列表,對(duì)于可選參數(shù)exc_info,我們會(huì)在start_response()調(diào)用及錯(cuò)誤處理章節(jié)進(jìn)行解釋。它僅在應(yīng)用程序捕獲錯(cuò)誤后并嘗試向?yàn)g覽器端顯示錯(cuò)誤時(shí)使用。
start_response必須返回一個(gè)可調(diào)用的對(duì)象 - write(body_data), 這個(gè)對(duì)象的調(diào)用需要一個(gè)可以作為HTTP響應(yīng)主體的字符串作為位置參數(shù)(注意:write僅用于支持某些現(xiàn)有的框架的輸出API,如果新的框架可以避免的話,就不應(yīng)該再使用,這一部分細(xì)節(jié)會(huì)在緩沖和流一節(jié)進(jìn)一步闡述)。
當(dāng)服務(wù)端調(diào)用框架端時(shí),框架端應(yīng)該返回包含0個(gè)或多個(gè)字符串的可迭代對(duì)象,這可以通過(guò)多種方式實(shí)現(xiàn),比如返回一個(gè)包含多個(gè)字符串的列表,或者一個(gè)產(chǎn)生字符串的生成器函數(shù),又或者是一個(gè)可迭代的實(shí)例對(duì)象。不管它是如何完成的,框架端都應(yīng)該返回滿足此要求的結(jié)果。
服務(wù)端必須在響應(yīng)另一個(gè)請(qǐng)求之前,把產(chǎn)生的字符串以無(wú)緩沖的方式傳輸?shù)娇蛻舳?。(換句話說(shuō),框架端應(yīng)該有它自己的緩沖區(qū),有關(guān)框架端如何處理輸出的更多信息,請(qǐng)參閱下面的緩沖和流一節(jié))。
服務(wù)端應(yīng)該把產(chǎn)生的字符串當(dāng)成二進(jìn)制字節(jié)碼來(lái)處理:特別是要確保行結(jié)束符不被修改??蚣芏藙t負(fù)責(zé)確保寫(xiě)入的字符串是適合客戶端的格式。服務(wù)端可以應(yīng)用HTTP傳輸編碼,或者為了實(shí)現(xiàn)諸如字節(jié)范圍傳輸?shù)腍TTP功能而執(zhí)行其他轉(zhuǎn)換。(參閱:其他的HTTP功能)
如果len(iterable)方法調(diào)用成功,那么服務(wù)端也會(huì)認(rèn)為結(jié)果是正確的,也就是說(shuō),如果框架提供了len方法,那它也必須返回一個(gè)與此方法相匹配的一個(gè)結(jié)果。(請(qǐng)參閱處理Content-Length頭部分了解如何正確使用。)
如果框架端返回的可迭代對(duì)象有close()方法的話,那么不管請(qǐng)求有沒(méi)有正常結(jié)束或者由于錯(cuò)誤提前中止,服務(wù)端都必須在完成當(dāng)前的請(qǐng)求后調(diào)用該方法(這個(gè)方法是為了支持框架端的一些資源釋放)。該協(xié)議旨在對(duì)PEP 325的生成器及其他擁有close()方法的可迭代對(duì)象進(jìn)行支持。
(注意:框架端必須在生成第一個(gè)響應(yīng)主體的字符串之前調(diào)用start_response方法,這樣服務(wù)端才能在發(fā)送任何主體內(nèi)容之前發(fā)送響應(yīng)頭,然而,這個(gè)調(diào)用可能是由迭代器的第一次迭代執(zhí)行的,所以服務(wù)器不能假定start_response()方法是在迭代器開(kāi)始迭代之前就已經(jīng)被調(diào)用了)
最后,服務(wù)端不能直接使用由框架端返回的可迭代對(duì)象中的任何屬性,除非它是一個(gè)特定服務(wù)器的實(shí)例,比如由wsgi.file_wrapper返回的file wrapper(請(qǐng)參閱:可選特定平臺(tái)的文件處理),一般情況下,只有這里指定的屬性或通過(guò)PEP 234文檔定義的API是可以使用的。
environ變量
environ字典需要包含如通用網(wǎng)關(guān)接口規(guī)范定義CGI環(huán)境變量。以下的變量是必須存在的,除非它們是空的字符串,在這種情況下,除非是另有說(shuō)明,否則它們可能會(huì)被忽略。
| 變量名 | 描述 |
|---|---|
| REQUEST_METHOD | HTTP請(qǐng)求方法,比如GET和POST。這個(gè)變量不能為空,所以總是需要的 |
| SCRIPT_NAME | 請(qǐng)求URL的路徑對(duì)應(yīng)于框架端的初始位置,框架端會(huì)根據(jù)這個(gè)變量找到對(duì)應(yīng)的虛擬位置。如果這個(gè)位置是框架端的根位置的話,那么這個(gè)變量可能是一個(gè)空字符串 |
| PATH_INFO | 請(qǐng)求的URL除去SCRIPT_NAME的剩余部分,指出了請(qǐng)求目標(biāo)在框架端的虛擬位置。如果請(qǐng)求的目標(biāo)是這個(gè)框架的根路徑且沒(méi)有“/”符號(hào)結(jié)尾的話,那么這個(gè)變量可能是一個(gè)空字符串。 |
| QUERY_STRING | URL中緊跟“?”后的那一部分。也可能是空的字符串或者不存在此變量。 |
| CONTENT_TYPE | HTTP請(qǐng)求頭Content-Type中的內(nèi)容, 可能是空的字符串或者不存在此變量。 |
| CONTENT_LENGTH | HTTP請(qǐng)求頭Content-Length中的內(nèi)容, 可能是空的字符串或者不存在此變量。 |
| SERVER_NAME SERVER_PORT | 當(dāng)這些變量和SCRIPT_NAME,PATH_INFO拼在一起時(shí),可以得到完整的URL。注意,如果存在HTTP_HOST的話,應(yīng)該優(yōu)先考慮使用HTTP_POST來(lái)重新構(gòu)造請(qǐng)求URL.請(qǐng)參閱URL重建部分獲取更多細(xì)節(jié)。SERVER_NAME, SERVER_PORT不能為空,所以總是需要的。 |
| SERVER_PROTOCOL | 用戶用來(lái)發(fā)送請(qǐng)求的協(xié)議版本,典型的有“HTTP/1.0”或者“HTTP/1.1”和由應(yīng)用程序用來(lái)確定如何處理的HTTP請(qǐng)求頭。(這個(gè)變量也可能叫做請(qǐng)求協(xié)議- REQUEST_PROTOCOL,因?yàn)樗硎镜氖钦?qǐng)求的協(xié)議,并不一定是服務(wù)端用于響應(yīng)的協(xié)議。不過(guò)為了和現(xiàn)有的通用網(wǎng)關(guān)接口兼容,我們?nèi)匀皇褂矛F(xiàn)在的這個(gè)變量名) |
| HTTP_VARIABLES | 變量對(duì)應(yīng)于客戶端提供的HTTP請(qǐng)求頭(比如以“HTTP_”開(kāi)頭的變量)這些變量的存在或不存在應(yīng)該與HTTP請(qǐng)求頭中的變量的存在或是不存在相對(duì)應(yīng)。 |
服務(wù)端應(yīng)該嘗試提供盡可能多適用的CGI變量。此外,如果使用了SSL,服務(wù)端還應(yīng)該提供一些相應(yīng)的Apache SSL環(huán)境變量。比如HTTPS = on和SSL_PROTOCOL。不過(guò),使用上面列出CGI變量以外的任何變量的應(yīng)用程序,對(duì)于不支持相關(guān)擴(kuò)展的Web服務(wù)器必然是不可移植的(例如,不提供文件發(fā)布服務(wù)的服務(wù)器就不能夠給出有意義的DOCUMENT_ROOT或PATH_TRANSLATED。)
一個(gè)兼容WSGI規(guī)范的服務(wù)器必以文檔的形式給出它所提供的變量,以及適當(dāng)?shù)亩x。框架端應(yīng)該檢查它所需要的任何變量是否存在,并且在有變量不存在的情況下有對(duì)應(yīng)的備用方案。
缺失的變量應(yīng)該排除在environ字典之外(比如沒(méi)有發(fā)生認(rèn)證時(shí)的REMOTE_USER),另外CGI定義的變量必須是字符串,如果CGI定義的變量為任意的其他類型而不是字符串的話都是違反本規(guī)范的。
除去CGI定義的變量,environ字典也可能包含任意的操作系統(tǒng)環(huán)境變量,并且必須包含如下WSGI定義的變量。
| 變量 | 值 |
|---|---|
| wsgi.version | 元組(1,0),表示W(wǎng)SGI的版本1.0 |
| wsgi.url_scheme | 表示正在調(diào)用框架端的URL協(xié)議部分的字符串,一般來(lái)說(shuō)值為“http”或“https”。 |
| wsgi.input | HTTP請(qǐng)求主體可以讀取的輸入流(類文件對(duì)象)。服務(wù)端可以根據(jù)框架端的請(qǐng)求按需讀取,或者預(yù)讀客戶端發(fā)來(lái)請(qǐng)求并緩存在內(nèi)存或磁盤(pán)中,或根據(jù)自己的偏好,用其他的方式提供的輸入流。 |
| wsgi.errors | 一個(gè)輸出流,可以寫(xiě)入錯(cuò)誤對(duì)象,用于在標(biāo)準(zhǔn)化及中心位置記錄程序及其他錯(cuò)誤。這應(yīng)該是一個(gè)“文本模式”流,比如框架端應(yīng)該用“\n”來(lái)做為行結(jié)束符,并且假定服務(wù)端可以正確處理。對(duì)于大多數(shù)的服務(wù)器,wsgi.errors將是服務(wù)器端主要的錯(cuò)誤日志,或者可能是sys.stderr,或某種類型的日志文件。服務(wù)器端應(yīng)該提供相應(yīng)文檔解釋如何配置及從哪里找到輸出日志。如果需要的話,服務(wù)器端也可以提供不同的錯(cuò)誤處理流給不同的框架端。 |
| wsgi.multithread | 如果框架端可以在一個(gè)進(jìn)程中同時(shí)被不同的線程所調(diào)用的話,那么應(yīng)該設(shè)為T(mén)RUE,否則為FALSE |
| wsgi.multiprocess | 如果框架端可以同時(shí)被另外一個(gè)進(jìn)程所調(diào)用的話,那么應(yīng)該設(shè)為T(mén)RUE,否則為FALSE |
| wsgi.run_once | 如果服務(wù)端期望框架端在一個(gè)進(jìn)程的生命周期中只被調(diào)用一次(但不能保證),那么值應(yīng)該為T(mén)RUE, 通常情況下,對(duì)于基于CGI(或類似的)的網(wǎng)關(guān)來(lái)說(shuō),這只會(huì)為T(mén)RUE。 |
最后,environ也可能包含服務(wù)端自定義變量,這些變量的變量名應(yīng)該只能由小寫(xiě)字母,數(shù)字,點(diǎn)和下劃線組成,并且應(yīng)該以定義的服務(wù)器或網(wǎng)關(guān)唯一的名稱作為前綴。例如,mod_python 自定義的變量可能為mod_python.some_variable的形式。
輸入和錯(cuò)誤流
服務(wù)端提供的輸入和錯(cuò)誤流必須支持以下方法。
| Method | Stream | Notes |
|---|---|---|
| read(size) | input | 1 |
| readline() | input | 1,2 |
| readlines(hint) | input | 1,3 |
| iter() | input | |
| flush | errors | 4 |
| write(str) | errors | |
| writelines(seq) | errors |
上述每種方法的定義可以參考Python庫(kù)中的定義,下面則是上表中Notes的一個(gè)說(shuō)明:
- 服務(wù)器不需要讀取超過(guò)客戶端指定長(zhǎng)度的內(nèi)容,如果客戶端嘗試讀取超過(guò)內(nèi)容長(zhǎng)度的點(diǎn)時(shí),服務(wù)端可以模擬一個(gè)文件結(jié)束條件強(qiáng)制結(jié)束??蚣芏瞬粦?yīng)該讀取超過(guò)content-length中定義的長(zhǎng)度的內(nèi)容。
- 可選的size參數(shù),對(duì)于這里的readline函數(shù)來(lái)說(shuō)是不支持的,因?yàn)槠洳惶子趯?shí)現(xiàn),在實(shí)際中也不太用的到。
- hint參數(shù)對(duì)于調(diào)用者和實(shí)現(xiàn)者來(lái)說(shuō)都是可選的,框架端可以不提供,服務(wù)端也可以忽略它。
- 因?yàn)殄e(cuò)誤可能不能重現(xiàn),所以服務(wù)端可以執(zhí)行寫(xiě)操作而不經(jīng)過(guò)緩沖區(qū)。在這種情況下,flush()可能是個(gè)空操作。不過(guò),框架端不能假設(shè)這個(gè)輸出是不緩沖的或flush()是個(gè)空操作。如果想要確保輸出被寫(xiě)入,則必須調(diào)用flush()方法。(例如:最大限度地減少來(lái)自多個(gè)進(jìn)程的數(shù)據(jù)混合寫(xiě)入一個(gè)相同的錯(cuò)誤日志)
上面列出的方法都必須被遵循這個(gè)規(guī)范的服務(wù)端支持,框架端也應(yīng)該不使用其他任何方法或input及errors對(duì)象的屬性。需要指出的是,框架端不要嘗試關(guān)閉這些流,即使它們擁有close()方法。
可調(diào)用的start_response函數(shù)
第二個(gè)傳給框架端的參數(shù)是一個(gè)可調(diào)用的函數(shù),它的調(diào)用形式為start_response(status, response_headers, exc_info = None)。(同所有的WSGI變量一樣,參數(shù)必須以位置參數(shù)的形式提供,而不是字典參數(shù)。)start_response通常用來(lái)開(kāi)始對(duì)HTTP請(qǐng)求做響應(yīng),它必須返回一個(gè)可調(diào)用的write(body_data)對(duì)象。(請(qǐng)參閱緩沖與流一節(jié))
status參數(shù)是一個(gè)HTTP狀態(tài)字符串,比如說(shuō)“200 OK”或“400 Not Found”。它是一個(gè)由狀態(tài)碼和一個(gè)簡(jiǎn)短的理由組成的一個(gè)字符串,并且按照狀態(tài)碼,理由的順序,中間由空格字符分開(kāi),沒(méi)有額外的空格字符或者其他的字符。(請(qǐng)查閱RFC 2616的6.1.1節(jié)獲取更多信息)。字符串一定不能包含控制字符,也不能用回車,行結(jié)束符或是它們的組合。
response_headers參數(shù)是一個(gè)元組列表。它必須是一個(gè)python內(nèi)置的列表對(duì)象;比如:type(response_headers) 就是一個(gè)list,服務(wù)端也可以按照它自己的需求去修改其中的值。每個(gè)header_name都必須是一個(gè)有效的HTTP響應(yīng)頭。(RFC 26164.2節(jié)中有定義),不以冒號(hào)結(jié)尾,也沒(méi)有其他的標(biāo)點(diǎn)符號(hào)。
每個(gè)響應(yīng)頭的值,無(wú)論在值中間還是結(jié)尾,都不能含有任何控制字符,包括回車和行結(jié)束符。(這些需求是為了最小化服務(wù)端或需要檢查、修改響應(yīng)頭的中間處理器,在執(zhí)行解析操作時(shí)的復(fù)雜性)。
一般來(lái)說(shuō),服務(wù)端需要確保發(fā)送給客戶端的是正確的響應(yīng)頭:如果框架端忽略了HTTP協(xié)議必須的響應(yīng)頭(或其他有效的規(guī)范),服務(wù)端必須在發(fā)送響應(yīng)時(shí)添加。例如,HTTP Date:和Server: 這兩個(gè)響應(yīng)頭正常來(lái)說(shuō)必須由服務(wù)端提供。
(這里給服務(wù)端開(kāi)發(fā)者一個(gè)提醒: HTTP響應(yīng)頭名字是大小寫(xiě)敏感的,所以在檢查框架端發(fā)來(lái)的響應(yīng)頭時(shí),應(yīng)該把這一部分也考慮進(jìn)去。)
框架和中間件中禁止使用HTTP/1.1中的“hop-by-hop”的首部或者類似的功能,以及任何在HTTP/1.0協(xié)議中與之相應(yīng)的功能或其他可能影響客戶端連接持久性的首部。這些功能與真正的WEB服務(wù)器的功能是獨(dú)立的,服務(wù)端如果接收到框架端嘗試發(fā)送類似的響應(yīng),應(yīng)該判定其發(fā)生了致命的錯(cuò)誤,并拋出異常。(關(guān)于“hop-by-hop”的更多細(xì)節(jié),請(qǐng)參閱其他的HTTP特征一節(jié))。
start_response不能直接傳輸響應(yīng)頭。相反,它必須存儲(chǔ)相關(guān)的信息,等到服務(wù)端拿到框架返回一個(gè)非空字符串,或者在框架端第一次調(diào)用write方法后,交由服務(wù)端處理,再發(fā)送給客戶端。換句話說(shuō),響應(yīng)頭必須等到有真正的傳輸主體時(shí)或直到框架端迭代完所有的主體內(nèi)容才能進(jìn)行傳輸。(唯一的例外是響應(yīng)頭中包含Content-Length,且其值為0.)
這樣延遲發(fā)送響應(yīng)頭是為了確保在發(fā)送響應(yīng)前的最后一刻,框架端也可以用錯(cuò)誤的輸出替換他們?cè)瓉?lái)的輸出。例如,當(dāng)框架端在生成響應(yīng)主體時(shí)發(fā)生錯(cuò)誤,那么就可能需要把響應(yīng)頭從“200 OK”改為“500 Internal Error”。
exc_info參數(shù),必須是一個(gè)Python的sys.exc_info()元組對(duì)象。這個(gè)參數(shù)只是在start_response在被錯(cuò)誤處理器調(diào)用時(shí),由框架端提供。如果exc_info存在并且HTTP響應(yīng)頭還沒(méi)有輸出時(shí),start_response應(yīng)該用新提供的響應(yīng)頭來(lái)替換現(xiàn)在已經(jīng)存儲(chǔ)的,因此通過(guò)這種方式可以在一個(gè)錯(cuò)誤發(fā)生時(shí),讓框架端有機(jī)會(huì)“改變主意”。
不過(guò),如果exc_info存在但是HTTP響應(yīng)頭也已經(jīng)發(fā)送出去,那么start_response應(yīng)該必須拋出一個(gè)異常,并且這個(gè)異常是由exc_info組成的元組。也就是:
raise exc_info[0], exc_info[1], exc_info[2]
這個(gè)再次拋出的異常會(huì)被框架再次捕獲,原則上應(yīng)該拋棄。(一旦HTTP請(qǐng)求頭已經(jīng)被發(fā)送,框架端試圖發(fā)送錯(cuò)誤輸出到瀏覽器是不安全的。)框架端不應(yīng)該捕獲任何由start_response拋出的異常。相反,框架端應(yīng)該允許把這些異常傳播給服務(wù)端。請(qǐng)參閱錯(cuò)誤處理一節(jié)查看更多細(xì)節(jié)。
框架可能會(huì)調(diào)用start_response不止一次,這種情況當(dāng)且僅當(dāng)提供了exc_info參數(shù)時(shí)才會(huì)發(fā)生。更準(zhǔn)確的說(shuō),如果在框架端已經(jīng)調(diào)用了start_response后,卻在再次調(diào)用時(shí)沒(méi)有提供exc_info參數(shù),這是一個(gè)致命的錯(cuò)誤。(請(qǐng)參閱上面的CGI網(wǎng)關(guān)的示例,了解正確的邏輯。)
注意:服務(wù)端、中間件在實(shí)現(xiàn)start_response方法時(shí),應(yīng)該確保在函數(shù)執(zhí)行期間沒(méi)有持續(xù)引用指向exc_info參數(shù),這也是為了避免在回溯時(shí)產(chǎn)生循環(huán)引用。最簡(jiǎn)單避免該情況的方法如下:
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
# do stuff w/exc_info here
finally:
exc_info = None # Avoid circular ref.
CGI網(wǎng)關(guān)的例子也很好的說(shuō)明了這個(gè)情況。
處理Content-Length首部
如果框架沒(méi)有提供Content-Length首部,服務(wù)端可能需要選擇一個(gè)方式去處理這種情況。最簡(jiǎn)單的方法是當(dāng)響應(yīng)結(jié)束時(shí),關(guān)閉客戶端連接。
某些情況下,服務(wù)端也許可以創(chuàng)建Content-Length首部, 或者至少避免關(guān)閉客戶端連接。如果框架不需要調(diào)用write(),并且返回的可迭代對(duì)象長(zhǎng)度為1,那么服務(wù)端就可以自動(dòng)用第一個(gè)可迭代對(duì)象中每個(gè)字符串的長(zhǎng)度來(lái)定義Content-Length的值。
如果服務(wù)端和客戶端都支持HTTP/1.1的“塊編碼”,那么服務(wù)端也可以用塊編碼來(lái)發(fā)送每一個(gè)write()或迭代對(duì)象中每一個(gè)字符串,并為每一個(gè)塊創(chuàng)建Content-Length,其值為塊長(zhǎng)度。這也允許服務(wù)端保持客戶端連接,當(dāng)然如果需要的話。注:當(dāng)服務(wù)端這樣使用時(shí),必須遵循RFC 2616協(xié)議,或者回退到Content-Length缺失的其他策略。
(注意:框架和中間件不需要實(shí)現(xiàn)任何一個(gè)Transfer-Encoding的輸出,比如塊編碼,gzipping;以及“hop-by-hop”操作,編碼這些屬于服務(wù)端的范圍,請(qǐng)參閱其他HTTP特征一節(jié)獲取細(xì)節(jié)。)