WSGI web服務網關協議(2)

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

前一部分內容請查看:http://10111000.com/2017/12/22/PEP333_1/

tips: 以下, 服務端代指服務端和網關, 框架代指應用端和框架端

規范細節

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

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

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

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

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

當服務端調用框架端時,框架端應該返回包含0個或多個字符串的可迭代對象,這可以通過多種方式實現,比如返回一個包含多個字符串的列表,或者一個產生字符串的生成器函數,又或者是一個可迭代的實例對象。不管它是如何完成的,框架端都應該返回滿足此要求的結果。

服務端必須在響應另一個請求之前,把產生的字符串以無緩沖的方式傳輸到客戶端。(換句話說,框架端應該有它自己的緩沖區,有關框架端如何處理輸出的更多信息,請參閱下面的緩沖和流一節)。

服務端應該把產生的字符串當成二進制字節碼來處理:特別是要確保行結束符不被修改。框架端則負責確保寫入的字符串是適合客戶端的格式。服務端可以應用HTTP傳輸編碼,或者為了實現諸如字節范圍傳輸的HTTP功能而執行其他轉換。(參閱:其他的HTTP功能)

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

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

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

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

environ變量

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

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

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

一個兼容WSGI規范的服務器必以文檔的形式給出它所提供的變量,以及適當的定義。框架端應該檢查它所需要的任何變量是否存在,并且在有變量不存在的情況下有對應的備用方案。

缺失的變量應該排除在environ字典之外(比如沒有發生認證時的REMOTE_USER),另外CGI定義的變量必須是字符串,如果CGI定義的變量為任意的其他類型而不是字符串的話都是違反本規范的。

除去CGI定義的變量,environ字典也可能包含任意的操作系統環境變量,并且必須包含如下WSGI定義的變量。

變量
wsgi.version 元組(1,0),表示WSGI的版本1.0
wsgi.url_scheme 表示正在調用框架端的URL協議部分的字符串,一般來說值為“http”或“https”。
wsgi.input HTTP請求主體可以讀取的輸入流(類文件對象)。服務端可以根據框架端的請求按需讀取,或者預讀客戶端發來請求并緩存在內存或磁盤中,或根據自己的偏好,用其他的方式提供的輸入流。
wsgi.errors 一個輸出流,可以寫入錯誤對象,用于在標準化及中心位置記錄程序及其他錯誤。這應該是一個“文本模式”流,比如框架端應該用“\n”來做為行結束符,并且假定服務端可以正確處理。對于大多數的服務器,wsgi.errors將是服務器端主要的錯誤日志,或者可能是sys.stderr,或某種類型的日志文件。服務器端應該提供相應文檔解釋如何配置及從哪里找到輸出日志。如果需要的話,服務器端也可以提供不同的錯誤處理流給不同的框架端。
wsgi.multithread 如果框架端可以在一個進程中同時被不同的線程所調用的話,那么應該設為TRUE,否則為FALSE
wsgi.multiprocess 如果框架端可以同時被另外一個進程所調用的話,那么應該設為TRUE,否則為FALSE
wsgi.run_once 如果服務端期望框架端在一個進程的生命周期中只被調用一次(但不能保證),那么值應該為TRUE, 通常情況下,對于基于CGI(或類似的)的網關來說,這只會為TRUE。

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

輸入和錯誤流

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

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. 服務器不需要讀取超過客戶端指定長度的內容,如果客戶端嘗試讀取超過內容長度的點時,服務端可以模擬一個文件結束條件強制結束。框架端不應該讀取超過content-length中定義的長度的內容。
  2. 可選的size參數,對于這里的readline函數來說是不支持的,因為其不太易于實現,在實際中也不太用的到。
  3. hint參數對于調用者和實現者來說都是可選的,框架端可以不提供,服務端也可以忽略它。
  4. 因為錯誤可能不能重現,所以服務端可以執行寫操作而不經過緩沖區。在這種情況下,flush()可能是個空操作。不過,框架端不能假設這個輸出是不緩沖的或flush()是個空操作。如果想要確保輸出被寫入,則必須調用flush()方法。(例如:最大限度地減少來自多個進程的數據混合寫入一個相同的錯誤日志)

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

可調用的start_response函數

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

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

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

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

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

(這里給服務端開發者一個提醒: HTTP響應頭名字是大小寫敏感的,所以在檢查框架端發來的響應頭時,應該把這一部分也考慮進去。)

框架和中間件中禁止使用HTTP/1.1中的“hop-by-hop”的首部或者類似的功能,以及任何在HTTP/1.0協議中與之相應的功能或其他可能影響客戶端連接持久性的首部。這些功能與真正的WEB服務器的功能是獨立的,服務端如果接收到框架端嘗試發送類似的響應,應該判定其發生了致命的錯誤,并拋出異常。(關于“hop-by-hop”的更多細節,請參閱其他的HTTP特征一節)。

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

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

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

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

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

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

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

注意:服務端、中間件在實現start_response方法時,應該確保在函數執行期間沒有持續引用指向exc_info參數,這也是為了避免在回溯時產生循環引用。最簡單避免該情況的方法如下:

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網關的例子也很好的說明了這個情況。

處理Content-Length首部

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

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

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

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,073評論 6 13
  • 在 從零開始搭建論壇(一):Web服務器與Web框架 中我們弄清楚了Web 服務器、Web 應用程序、Web框架的...
    selfboot閱讀 2,293評論 0 8
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 昨日聽聞奶奶生病住院的消息,當時并沒有特別的悲傷和難過,而是一遍遍機械地撥打爸媽的電話,通過他們去打聽我叔叔的聯系...
    黑桃K先森閱讀 323評論 0 1