WSGI web服務(wù)網(wǎng)關(guān)協(xié)議(2)

本文譯自:https://www.python.org/dev/peps/pep-0333

前一部分內(nèi)容請查看:http://10111000.com/2017/12/22/PEP333_1/

tips: 以下, 服務(wù)端代指服務(wù)端和網(wǎng)關(guān), 框架代指應(yīng)用端和框架端

規(guī)范細(xì)節(jié)

應(yīng)用對象必須接收兩個位置參數(shù)。為了更好的說明,我們把兩個變量分別命名為environ和start_response,但是這個規(guī)范里并不要求和變量名完全一致。服務(wù)器端必須傳入相應(yīng)的位置參數(shù)(不是字典參數(shù))去調(diào)用應(yīng)用對象。(如上所述,我們可以以這種方式進(jìn)行調(diào)用:result = application(environ, start_response)。)

environ是一個字典對象,包含CGI風(fēng)格的環(huán)境變量,這個對象必須是一個內(nèi)置的python字典(不是一個子類,用戶自定義的字典或其他擁有類似字典操作的對象),且可以被框架端任意修改成它想要的形式。這個environ也必須包含某些WSGI所需要的變量(稍后會加以詳述),也可能包含服務(wù)器的某些擴(kuò)展變量,按照慣例,如何命名將會在下文提及。

start_response是一個可接受兩個位置參數(shù),一個可選參數(shù)的可調(diào)用對象。同樣為了說明,這個三個參數(shù)分別命名為status, response_headers 和 exc_info,它們也可以有不同的命名,框架端必須傳入對應(yīng)的位置參數(shù)去調(diào)用start_response方法。(例如:start_response(status, response_headers))

status參數(shù)是一個形如“999 message here”的狀態(tài)字符串,response_headers是由HTTP響應(yīng)頭形成的元組(header_name, header_value)在一起組成的列表,對于可選參數(shù)exc_info,我們會在start_response()調(diào)用及錯誤處理章節(jié)進(jìn)行解釋。它僅在應(yīng)用程序捕獲錯誤后并嘗試向?yàn)g覽器端顯示錯誤時使用。

start_response必須返回一個可調(diào)用的對象 - write(body_data), 這個對象的調(diào)用需要一個可以作為HTTP響應(yīng)主體的字符串作為位置參數(shù)(注意:write僅用于支持某些現(xiàn)有的框架的輸出API,如果新的框架可以避免的話,就不應(yīng)該再使用,這一部分細(xì)節(jié)會在緩沖和流一節(jié)進(jìn)一步闡述)。

當(dāng)服務(wù)端調(diào)用框架端時,框架端應(yīng)該返回包含0個或多個字符串的可迭代對象,這可以通過多種方式實(shí)現(xiàn),比如返回一個包含多個字符串的列表,或者一個產(chǎn)生字符串的生成器函數(shù),又或者是一個可迭代的實(shí)例對象。不管它是如何完成的,框架端都應(yīng)該返回滿足此要求的結(jié)果。

服務(wù)端必須在響應(yīng)另一個請求之前,把產(chǎn)生的字符串以無緩沖的方式傳輸?shù)娇蛻舳恕#〒Q句話說,框架端應(yīng)該有它自己的緩沖區(qū),有關(guān)框架端如何處理輸出的更多信息,請參閱下面的緩沖和流一節(jié))。

服務(wù)端應(yīng)該把產(chǎn)生的字符串當(dāng)成二進(jìn)制字節(jié)碼來處理:特別是要確保行結(jié)束符不被修改??蚣芏藙t負(fù)責(zé)確保寫入的字符串是適合客戶端的格式。服務(wù)端可以應(yīng)用HTTP傳輸編碼,或者為了實(shí)現(xiàn)諸如字節(jié)范圍傳輸?shù)腍TTP功能而執(zhí)行其他轉(zhuǎn)換。(參閱:其他的HTTP功能)

如果len(iterable)方法調(diào)用成功,那么服務(wù)端也會認(rèn)為結(jié)果是正確的,也就是說,如果框架提供了len方法,那它也必須返回一個與此方法相匹配的一個結(jié)果。(請參閱處理Content-Length頭部分了解如何正確使用。)

如果框架端返回的可迭代對象有close()方法的話,那么不管請求有沒有正常結(jié)束或者由于錯誤提前中止,服務(wù)端都必須在完成當(dāng)前的請求后調(diào)用該方法(這個方法是為了支持框架端的一些資源釋放)。該協(xié)議旨在對PEP 325的生成器及其他擁有close()方法的可迭代對象進(jìn)行支持。

(注意:框架端必須在生成第一個響應(yīng)主體的字符串之前調(diào)用start_response方法,這樣服務(wù)端才能在發(fā)送任何主體內(nèi)容之前發(fā)送響應(yīng)頭,然而,這個調(diào)用可能是由迭代器的第一次迭代執(zhí)行的,所以服務(wù)器不能假定start_response()方法是在迭代器開始迭代之前就已經(jīng)被調(diào)用了)

最后,服務(wù)端不能直接使用由框架端返回的可迭代對象中的任何屬性,除非它是一個特定服務(wù)器的實(shí)例,比如由wsgi.file_wrapper返回的file wrapper(請參閱:可選特定平臺的文件處理),一般情況下,只有這里指定的屬性或通過PEP 234文檔定義的API是可以使用的。

environ變量

environ字典需要包含如通用網(wǎng)關(guān)接口規(guī)范定義CGI環(huán)境變量。以下的變量是必須存在的,除非它們是空的字符串,在這種情況下,除非是另有說明,否則它們可能會被忽略。

變量名 描述
REQUEST_METHOD HTTP請求方法,比如GET和POST。這個變量不能為空,所以總是需要的
SCRIPT_NAME 請求URL的路徑對應(yīng)于框架端的初始位置,框架端會根據(jù)這個變量找到對應(yīng)的虛擬位置。如果這個位置是框架端的根位置的話,那么這個變量可能是一個空字符串
PATH_INFO 請求的URL除去SCRIPT_NAME的剩余部分,指出了請求目標(biāo)在框架端的虛擬位置。如果請求的目標(biāo)是這個框架的根路徑且沒有“/”符號結(jié)尾的話,那么這個變量可能是一個空字符串。
QUERY_STRING URL中緊跟“?”后的那一部分。也可能是空的字符串或者不存在此變量。
CONTENT_TYPE HTTP請求頭Content-Type中的內(nèi)容, 可能是空的字符串或者不存在此變量。
CONTENT_LENGTH HTTP請求頭Content-Length中的內(nèi)容, 可能是空的字符串或者不存在此變量。
SERVER_NAME SERVER_PORT 當(dāng)這些變量和SCRIPT_NAME,PATH_INFO拼在一起時,可以得到完整的URL。注意,如果存在HTTP_HOST的話,應(yīng)該優(yōu)先考慮使用HTTP_POST來重新構(gòu)造請求URL.請參閱URL重建部分獲取更多細(xì)節(jié)。SERVER_NAME, SERVER_PORT不能為空,所以總是需要的。
SERVER_PROTOCOL 用戶用來發(fā)送請求的協(xié)議版本,典型的有“HTTP/1.0”或者“HTTP/1.1”和由應(yīng)用程序用來確定如何處理的HTTP請求頭。(這個變量也可能叫做請求協(xié)議- REQUEST_PROTOCOL,因?yàn)樗硎镜氖钦埱蟮膮f(xié)議,并不一定是服務(wù)端用于響應(yīng)的協(xié)議。不過為了和現(xiàn)有的通用網(wǎng)關(guān)接口兼容,我們?nèi)匀皇褂矛F(xiàn)在的這個變量名)
HTTP_VARIABLES 變量對應(yīng)于客戶端提供的HTTP請求頭(比如以“HTTP_”開頭的變量)這些變量的存在或不存在應(yīng)該與HTTP請求頭中的變量的存在或是不存在相對應(yīng)。

服務(wù)端應(yīng)該嘗試提供盡可能多適用的CGI變量。此外,如果使用了SSL,服務(wù)端還應(yīng)該提供一些相應(yīng)的Apache SSL環(huán)境變量。比如HTTPS = on和SSL_PROTOCOL。不過,使用上面列出CGI變量以外的任何變量的應(yīng)用程序,對于不支持相關(guān)擴(kuò)展的Web服務(wù)器必然是不可移植的(例如,不提供文件發(fā)布服務(wù)的服務(wù)器就不能夠給出有意義的DOCUMENT_ROOT或PATH_TRANSLATED。)

一個兼容WSGI規(guī)范的服務(wù)器必以文檔的形式給出它所提供的變量,以及適當(dāng)?shù)亩x。框架端應(yīng)該檢查它所需要的任何變量是否存在,并且在有變量不存在的情況下有對應(yīng)的備用方案。

缺失的變量應(yīng)該排除在environ字典之外(比如沒有發(fā)生認(rèn)證時的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é)議部分的字符串,一般來說值為“http”或“https”。
wsgi.input HTTP請求主體可以讀取的輸入流(類文件對象)。服務(wù)端可以根據(jù)框架端的請求按需讀取,或者預(yù)讀客戶端發(fā)來請求并緩存在內(nèi)存或磁盤中,或根據(jù)自己的偏好,用其他的方式提供的輸入流。
wsgi.errors 一個輸出流,可以寫入錯誤對象,用于在標(biāo)準(zhǔn)化及中心位置記錄程序及其他錯誤。這應(yīng)該是一個“文本模式”流,比如框架端應(yīng)該用“\n”來做為行結(jié)束符,并且假定服務(wù)端可以正確處理。對于大多數(shù)的服務(wù)器,wsgi.errors將是服務(wù)器端主要的錯誤日志,或者可能是sys.stderr,或某種類型的日志文件。服務(wù)器端應(yīng)該提供相應(yīng)文檔解釋如何配置及從哪里找到輸出日志。如果需要的話,服務(wù)器端也可以提供不同的錯誤處理流給不同的框架端。
wsgi.multithread 如果框架端可以在一個進(jìn)程中同時被不同的線程所調(diào)用的話,那么應(yīng)該設(shè)為TRUE,否則為FALSE
wsgi.multiprocess 如果框架端可以同時被另外一個進(jìn)程所調(diào)用的話,那么應(yīng)該設(shè)為TRUE,否則為FALSE
wsgi.run_once 如果服務(wù)端期望框架端在一個進(jìn)程的生命周期中只被調(diào)用一次(但不能保證),那么值應(yīng)該為TRUE, 通常情況下,對于基于CGI(或類似的)的網(wǎng)關(guān)來說,這只會為TRUE。

最后,environ也可能包含服務(wù)端自定義變量,這些變量的變量名應(yīng)該只能由小寫字母,數(shù)字,點(diǎn)和下劃線組成,并且應(yīng)該以定義的服務(wù)器或網(wǎng)關(guān)唯一的名稱作為前綴。例如,mod_python 自定義的變量可能為mod_python.some_variable的形式。

輸入和錯誤流

服務(wù)端提供的輸入和錯誤流必須支持以下方法。

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庫中的定義,下面則是上表中Notes的一個說明:

  1. 服務(wù)器不需要讀取超過客戶端指定長度的內(nèi)容,如果客戶端嘗試讀取超過內(nèi)容長度的點(diǎn)時,服務(wù)端可以模擬一個文件結(jié)束條件強(qiáng)制結(jié)束。框架端不應(yīng)該讀取超過content-length中定義的長度的內(nèi)容。
  2. 可選的size參數(shù),對于這里的readline函數(shù)來說是不支持的,因?yàn)槠洳惶子趯?shí)現(xiàn),在實(shí)際中也不太用的到。
  3. hint參數(shù)對于調(diào)用者和實(shí)現(xiàn)者來說都是可選的,框架端可以不提供,服務(wù)端也可以忽略它。
  4. 因?yàn)殄e誤可能不能重現(xiàn),所以服務(wù)端可以執(zhí)行寫操作而不經(jīng)過緩沖區(qū)。在這種情況下,flush()可能是個空操作。不過,框架端不能假設(shè)這個輸出是不緩沖的或flush()是個空操作。如果想要確保輸出被寫入,則必須調(diào)用flush()方法。(例如:最大限度地減少來自多個進(jìn)程的數(shù)據(jù)混合寫入一個相同的錯誤日志)

上面列出的方法都必須被遵循這個規(guī)范的服務(wù)端支持,框架端也應(yīng)該不使用其他任何方法或input及errors對象的屬性。需要指出的是,框架端不要嘗試關(guān)閉這些流,即使它們擁有close()方法。

可調(diào)用的start_response函數(shù)

第二個傳給框架端的參數(shù)是一個可調(diào)用的函數(shù),它的調(diào)用形式為start_response(status, response_headers, exc_info = None)。(同所有的WSGI變量一樣,參數(shù)必須以位置參數(shù)的形式提供,而不是字典參數(shù)。)start_response通常用來開始對HTTP請求做響應(yīng),它必須返回一個可調(diào)用的write(body_data)對象。(請參閱緩沖與流一節(jié))

status參數(shù)是一個HTTP狀態(tài)字符串,比如說“200 OK”或“400 Not Found”。它是一個由狀態(tài)碼和一個簡短的理由組成的一個字符串,并且按照狀態(tài)碼,理由的順序,中間由空格字符分開,沒有額外的空格字符或者其他的字符。(請查閱RFC 2616的6.1.1節(jié)獲取更多信息)。字符串一定不能包含控制字符,也不能用回車,行結(jié)束符或是它們的組合。

response_headers參數(shù)是一個元組列表。它必須是一個python內(nèi)置的列表對象;比如:type(response_headers) 就是一個list,服務(wù)端也可以按照它自己的需求去修改其中的值。每個header_name都必須是一個有效的HTTP響應(yīng)頭。(RFC 26164.2節(jié)中有定義),不以冒號結(jié)尾,也沒有其他的標(biāo)點(diǎn)符號。

每個響應(yīng)頭的值,無論在值中間還是結(jié)尾,都不能含有任何控制字符,包括回車和行結(jié)束符。(這些需求是為了最小化服務(wù)端或需要檢查、修改響應(yīng)頭的中間處理器,在執(zhí)行解析操作時的復(fù)雜性)。

一般來說,服務(wù)端需要確保發(fā)送給客戶端的是正確的響應(yīng)頭:如果框架端忽略了HTTP協(xié)議必須的響應(yīng)頭(或其他有效的規(guī)范),服務(wù)端必須在發(fā)送響應(yīng)時添加。例如,HTTP Date:和Server: 這兩個響應(yīng)頭正常來說必須由服務(wù)端提供。

(這里給服務(wù)端開發(fā)者一個提醒: HTTP響應(yīng)頭名字是大小寫敏感的,所以在檢查框架端發(fā)來的響應(yīng)頭時,應(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ā)生了致命的錯誤,并拋出異常。(關(guān)于“hop-by-hop”的更多細(xì)節(jié),請參閱其他的HTTP特征一節(jié))。

start_response不能直接傳輸響應(yīng)頭。相反,它必須存儲相關(guān)的信息,等到服務(wù)端拿到框架返回一個非空字符串,或者在框架端第一次調(diào)用write方法后,交由服務(wù)端處理,再發(fā)送給客戶端。換句話說,響應(yīng)頭必須等到有真正的傳輸主體時或直到框架端迭代完所有的主體內(nèi)容才能進(jìn)行傳輸。(唯一的例外是響應(yīng)頭中包含Content-Length,且其值為0.)

這樣延遲發(fā)送響應(yīng)頭是為了確保在發(fā)送響應(yīng)前的最后一刻,框架端也可以用錯誤的輸出替換他們原來的輸出。例如,當(dāng)框架端在生成響應(yīng)主體時發(fā)生錯誤,那么就可能需要把響應(yīng)頭從“200 OK”改為“500 Internal Error”。

exc_info參數(shù),必須是一個Python的sys.exc_info()元組對象。這個參數(shù)只是在start_response在被錯誤處理器調(diào)用時,由框架端提供。如果exc_info存在并且HTTP響應(yīng)頭還沒有輸出時,start_response應(yīng)該用新提供的響應(yīng)頭來替換現(xiàn)在已經(jīng)存儲的,因此通過這種方式可以在一個錯誤發(fā)生時,讓框架端有機(jī)會“改變主意”。

不過,如果exc_info存在但是HTTP響應(yīng)頭也已經(jīng)發(fā)送出去,那么start_response應(yīng)該必須拋出一個異常,并且這個異常是由exc_info組成的元組。也就是:

raise exc_info[0], exc_info[1], exc_info[2]

這個再次拋出的異常會被框架再次捕獲,原則上應(yīng)該拋棄。(一旦HTTP請求頭已經(jīng)被發(fā)送,框架端試圖發(fā)送錯誤輸出到瀏覽器是不安全的。)框架端不應(yīng)該捕獲任何由start_response拋出的異常。相反,框架端應(yīng)該允許把這些異常傳播給服務(wù)端。請參閱錯誤處理一節(jié)查看更多細(xì)節(jié)。

框架可能會調(diào)用start_response不止一次,這種情況當(dāng)且僅當(dāng)提供了exc_info參數(shù)時才會發(fā)生。更準(zhǔn)確的說,如果在框架端已經(jīng)調(diào)用了start_response后,卻在再次調(diào)用時沒有提供exc_info參數(shù),這是一個致命的錯誤。(請參閱上面的CGI網(wǎng)關(guān)的示例,了解正確的邏輯。)

注意:服務(wù)端、中間件在實(shí)現(xiàn)start_response方法時,應(yīng)該確保在函數(shù)執(zhí)行期間沒有持續(xù)引用指向exc_info參數(shù),這也是為了避免在回溯時產(chǎn)生循環(huá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)的例子也很好的說明了這個情況。

處理Content-Length首部

如果框架沒有提供Content-Length首部,服務(wù)端可能需要選擇一個方式去處理這種情況。最簡單的方法是當(dāng)響應(yīng)結(jié)束時,關(guān)閉客戶端連接。

某些情況下,服務(wù)端也許可以創(chuàng)建Content-Length首部, 或者至少避免關(guān)閉客戶端連接。如果框架不需要調(diào)用write(),并且返回的可迭代對象長度為1,那么服務(wù)端就可以自動用第一個可迭代對象中每個字符串的長度來定義Content-Length的值。

如果服務(wù)端和客戶端都支持HTTP/1.1的“塊編碼”,那么服務(wù)端也可以用塊編碼來發(fā)送每一個write()或迭代對象中每一個字符串,并為每一個塊創(chuàng)建Content-Length,其值為塊長度。這也允許服務(wù)端保持客戶端連接,當(dāng)然如果需要的話。注:當(dāng)服務(wù)端這樣使用時,必須遵循RFC 2616協(xié)議,或者回退到Content-Length缺失的其他策略。

(注意:框架和中間件不需要實(shí)現(xiàn)任何一個Transfer-Encoding的輸出,比如塊編碼,gzipping;以及“hop-by-hop”操作,編碼這些屬于服務(wù)端的范圍,請參閱其他HTTP特征一節(jié)獲取細(xì)節(jié)。)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,711評論 19 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,537評論 6 13
  • 在 從零開始搭建論壇(一):Web服務(wù)器與Web框架 中我們弄清楚了Web 服務(wù)器、Web 應(yīng)用程序、Web框架的...
    selfboot閱讀 2,395評論 0 8
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,854評論 18 399
  • 昨日聽聞奶奶生病住院的消息,當(dāng)時并沒有特別的悲傷和難過,而是一遍遍機(jī)械地?fù)艽虬謰尩碾娫挘ㄟ^他們?nèi)ゴ蚵犖沂迨宓穆?lián)系...
    黑桃K先森閱讀 351評論 0 1

友情鏈接更多精彩內(nèi)容