參考:wsgi規範python
python當前擁有各類各樣的web應用程序框架,例如django,flask,twisted,tornado等。這對於新入python web坑的用戶而言,各類各樣的選擇多是個問題,由於一般來講,他們對於web框架的選擇將限制他們對於可用web服務器的選擇,反之,對於web服務器開發者而言,也是同樣。
相比之下,儘管Java也擁有着許多可用的web應用程序框架,可是Java的 Servlet API 使得使用任何Java web應用程序框架編寫的應用程序均可以在支持Servlet API的任何web服務器中運行。
此類API在python網絡服務器中的可用性和普遍性使得不管這些服務器是用python寫的,仍是經過網關協議(例如cgi,fastcig等),將框架的選擇與web服務器分開,讓開發者能夠自由選擇合適他們的配對,同時讓框架和服務器開發者能夠將精力集中在他們各自的專業領域中。所以PEP提出了web服務器與web應用程序或框架之間的簡單且通用的接口:web
the Python Web Server Gateway Interface (WSGI).
WSGI接口具備兩個方面:服務器端和應用程序端(也能夠叫框架)。
服務器端調用應用程序端提供的可調用對象。該對象的提供方式具體取決於服務器。假定某些服務器將須要應用程序提供者寫一個簡短的腳原本建立服務器或者網關的實例,併爲其提供應用程序對象,則其餘服務器可使用配置文件或其餘機制來指定應該從何處導入或以其餘方式獲取應用程序對象。
除了「純」服務器/網關和應用程序/框架外,還能夠建立實現本規範兩面的「中間件」組件。這些組件充當其所包含的服務器的應用程序,而且充當所包含的應用程序的服務器,而且能夠用於提供擴展的API,內容轉換,導航和其餘有用的功能。
咱們將使用術語「可調用」來表示「具備__call__方法的函數,方法,類或實例」。由實現可調用對象的服務器,網關或應用程序來選擇適合其須要的合適的實現技術。相反,正在調用可調用對象的服務器,網關或應用程序必須不依賴於向其提供了哪一種可調用對象。可調用對象僅被調用,而不是自省的。django
一般,HTTP處理字節,這意味着此規範主要是關於處理字節的。flask
可是,這些字節的內容一般具備某種文本解釋,在Python中,字符串是處理文本的最便捷方法。瀏覽器
可是在許多Python版本和實現中,字符串是Unicode而不是字節。這須要在可用的API和HTTP上下文中的字節和文本之間正確轉換之間保持謹慎的平衡……尤爲是要支持在具備不一樣str類型的Python實現之間移植代碼。服務器
所以,WSGI定義了兩種「字符串」:網絡
可是請不要感到困惑:即便Python的str類型其實是「幕後的」 Unicode,本地字符串的_內容_仍必須能夠經過Latin-1編碼轉換爲字節!(有關更多詳細信息,請參閱本文檔後面的「Unicode問題」部分。)app
簡而言之:在本文檔中看到「字符串」一詞時,它指的是「本地」字符串,即str類型的對象,不管它是在內部實現爲字節仍是Unicode。在您看到對「 bytestring」的引用時,應將其理解爲「在Python 3下爲字節類型的對象,在Python 2下爲str類型的對象」。框架
所以,即便HTTP在某種意義上「實際上只是字節」,使用任何Python的默認str類型都是有許多API便利。函數
應用程序對象是能夠接收兩個參數的可調用對象。這裏對象不該該誤解爲須要實際的對象實例,而應理解爲 函數,方法,或具備__call__ 方法的類的實例均可以用做應用程序對象。應用程序對象必須可以屢次調用,由於幾乎全部的服務器都會發出此類重複請求。
如下是兩個應用程序對象示例,一個是函數,一個是類:
HELLO_WORLD = b"Hello world!\n" def simple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [HELLO_WORLD] class AppClass: """Produce the same output, but using a class (Note: 'AppClass' is the "application" here, so calling it returns an instance of 'AppClass', which is then the iterable return value of the "application callable" as required by the spec. If we wanted to use *instances* of 'AppClass' as application objects instead, we would have to implement a '__call__' method, which would be invoked to execute the application, and we would need to create an instance for use by the server or gateway. """ def __init__(self, environ, start_response): self.environ = environ self.start = start_response def __iter__(self): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield HELLO_WORLD
服務器或網關針對從HTTP客戶端收到的針對該應用程序的每一個請求,調用一次可調用的應用程序。爲了說明,這是一個簡單的CGI網關,實現爲帶有應用程序對象的功能。請注意,此簡單示例的錯誤處理受到限制,由於默認狀況下,未捕獲的異常將轉儲到sys.stderr並由Web服務器記錄。
import os import sys enc, esc = sys.getfilesystemencoding(), 'surrogateescape' def unicode_to_wsgi(u): # Convert an environment variable to a WSGI "bytes-as-unicode" string return u.encode(enc, esc).decode('iso-8859-1') def wsgi_to_bytes(s): return s.encode('iso-8859-1') def run_with_cgi(application): environ = {k: unicode_to_wsgi(v) for k, v in os.environ.items()} environ['wsgi.input'] = sys.stdin.buffer environ['wsgi.errors'] = sys.stderr environ['wsgi.version'] = (1, 0) environ['wsgi.multithread'] = False environ['wsgi.multiprocess'] = True environ['wsgi.run_once'] = True if environ.get('HTTPS', 'off') in ('on', '1'): environ['wsgi.url_scheme'] = 'https' else: environ['wsgi.url_scheme'] = 'http' headers_set = [] headers_sent = [] def write(data): out = sys.stdout.buffer if not headers_set: raise AssertionError("write() before start_response()") elif not headers_sent: # Before the first output, send the stored headers status, response_headers = headers_sent[:] = headers_set out.write(wsgi_to_bytes('Status: %s\r\n' % status)) for header in response_headers: out.write(wsgi_to_bytes('%s: %s\r\n' % header)) out.write(wsgi_to_bytes('\r\n')) out.write(data) out.flush() def start_response(status, response_headers, exc_info=None): if exc_info: try: if headers_sent: # Re-raise original exception if headers sent raise exc_info[1].with_traceback(exc_info[2]) finally: exc_info = None # avoid dangling circular ref elif headers_set: raise AssertionError("Headers already set!") headers_set[:] = [status, response_headers] # Note: error checking on the headers should happen here, # *after* the headers are set. That way, if an error # occurs, start_response can only be re-called with # exc_info set. return write result = application(environ, start_response) try: for data in result: if data: # don't send headers until body appears write(data) if not headers_sent: write('') # send headers now if body was empty finally: if hasattr(result, 'close'): result.close()
注意,相對於某些應用程序,單個對象能夠扮演服務器的角色,而對於某些服務器而言,它也能夠充當應用程序。此類「中間件」組件可執行如下功能:
一般,中間件的存在對接口的「服務器/網關」和「應用程序/框架」而言都是透明的,而且不須要任何特殊支持。但願將中間件合併到應用程序中的用戶只需將中間件組件提供給服務器(就好像它是一個應用程序同樣),而後將中間件組件配置爲調用該應用程序,就好像中間件組件是服務器同樣。固然,中間件包裝的「應用程序」實際上多是包裝另外一個應用程序的另外一箇中間件組件,依此類推,從而建立了所謂的「中間件堆棧」。
在大多數狀況下,中間件必須符合WSGI的服務器端和應用程序端的限制和要求。可是,在某些狀況下,對中間件的要求比對「純」服務器或應用程序的要求更爲嚴格,這些要點將在規範中予以說明。
這是一箇中間件組件的示例(中間例子),該組件使用Joe Strout的piglatin.py將文本/純文本響應轉換爲PigLatin。(注意:「真正的」中間件組件可能會使用更健壯的方法來檢查內容類型,而且還應該檢查內容編碼。此外,此簡單示例忽略了單詞可能跨塊邊界拆分的可能性。 )
from piglatin import piglatin class LatinIter: """Transform iterated output to piglatin, if it's okay to do so Note that the "okayness" can change until the application yields its first non-empty bytestring, so 'transform_ok' has to be a mutable truth value. """ def __init__(self, result, transform_ok): if hasattr(result, 'close'): self.close = result.close self._next = iter(result).__next__ self.transform_ok = transform_ok def __iter__(self): return self def __next__(self): if self.transform_ok: return piglatin(self._next()) # call must be byte-safe on Py3 else: return self._next() class Latinator: # by default, don't transform output transform = False def __init__(self, application): self.application = application def __call__(self, environ, start_response): transform_ok = [] def start_latin(status, response_headers, exc_info=None): # Reset ok flag, in case this is a repeat call del transform_ok[:] for name, value in response_headers: if name.lower() == 'content-type' and value == 'text/plain': transform_ok.append(True) # Strip content-length if present, else it'll be wrong response_headers = [(name, value) for name, value in response_headers if name.lower() != 'content-length' ] break write = start_response(status, response_headers, exc_info) if transform_ok: def write_latin(data): write(piglatin(data)) # call must be byte-safe on Py3 return write_latin else: return write return LatinIter(self.application(environ, start_latin), transform_ok) # Run foo_app under a Latinator's control, using the example CGI gateway from foo_app import foo_app run_with_cgi(Latinator(foo_app))
規格明細
應用程序必須能接收兩個位置參數,爲了說明起見,咱們將這兩個參數命名爲environ和start_response,服務器必須使用位置參數調用應用程序對象。例如經過調用result = application(environ,start_response)
該ENVIRON參數是一個字典對象,包含CGI風格的環境變量。該對象必須是內置的Python字典(_而不是_子類,UserDict或其餘字典仿真),而且容許應用程序以所需的任何方式修改字典。該字典還必須包含某些WSGI必需的變量(在後面的部分中進行描述),而且還能夠包含服務器特定的擴展變量,這些擴展變量是根據如下將要描述的約定命名的。
所述start_response參數是一個可調用接受兩個必需的位置參數,以及一個可選的參數。爲了便於說明,咱們已將這些參數命名爲status,response_headers和exc__info,可是它們不須要具備這些名稱,而且應用程序必須使用位置參數(例如start_response(status,response_headers))調用start_response可調用對象。
所述start_response參數是一個可調用接受兩個必需的位置參數,以及一個可選的參數。爲了便於說明,咱們已將這些參數命名爲status,response_headers和exc_info,可是它們不須要具備這些名稱,而且應用程序必須使用位置參數(例如start_response(status,response_headers))調用start_response可調用對象。
該狀態參數是如下形式的狀態字符串「在這裏999消息」,和response_headers是列表(信頭,header_value)描述的HTTP響應報頭元組。可選的exc_info參數在如下有關start_response()可調用和錯誤處理的部分中進行了描述。僅當應用程序捕獲了錯誤並試圖向瀏覽器顯示錯誤消息時,才使用它。
所述start_response調用必須返回一個寫(body_data)可調用,它有一個位置參數:一個字節串被寫入做爲HTTP響應身體的一部分。(注意:提供write()可調用性僅是爲了支持某些現有框架的命令性輸出API;若是能夠避免,則不該由新應用程序或框架使用它。有關更多詳細信息,請參見「緩衝和流傳輸」部分。)
當服務器調用該應用程序對象時,該應用程序對象必須返回一個可迭代的結果,產生零個或多個字節串。這能夠經過多種方式來完成,例如,經過返回字節串列表,或者經過應用程序爲產生字節串的生成器函數,或者經過應用程序爲實例可迭代的類。不管如何完成,應用程序對象都必須始終返回可迭代的結果,產生零個或多個字節串。
服務器或網關必須以無緩衝的方式將產生的字節串傳輸到客戶端,在請求另外一個字節串以前完成每一個字節串的傳輸。(換句話說,應用程序應該執行本身的緩衝。有關如何處理應用程序輸出的更多信息,請參見下面的「緩衝和流傳輸」部分。)
服務器或網關應將產生的字節串視爲二進制字節序列:特別是,應確保不更改行尾。應用程序負責確保要寫入的字節串採用適合客戶端的格式。(服務器或網關能夠應用HTTP傳輸編碼,或執行其餘轉換,以實現HTTP功能(例如字節範圍傳輸)。有關更多詳細信息,請參見下面的其餘HTTP功能。)
若是對len(iterable)的調用成功,則服務器必須可以依靠準確的結果。也就是說,若是應用程序返回的iterable提供了有效的__len__()方法,則它必須返回準確的結果。(有關正常使用此方法的信息,請參見「處理內容長度標題」部分。)
若是應用程序返回的可迭代對象具備close()方法,則服務器或網關必須在當前請求完成後調用該方法,不管請求是否已正常完成,仍是因爲迭代期間的應用程序錯誤或提前斷開鏈接而提早終止瀏覽器。(close()方法的要求是支持應用程序釋放資源。該協議旨在經過close()方法來補充PEP 342的生成器支持以及其餘常見的可迭代項。)
返回生成器或其餘自定義迭代器的應用程序不該假定整個迭代器都將被消耗,由於服務器可能會提早關閉它。
(注意:應用程序必須在iterable產生其第一個主體字節串以前調用start_response(),以便服務器能夠在任何主體內容以前發送標頭。可是,此調用能夠在iterable的第一次迭代中執行,所以服務器必須在開始迭代可迭代對象以前,不要假設已調用了start_response()。)
最後,服務器和網關不得直接使用應用程序返回的iterable的任何其餘屬性,除非它是特定於該服務器或網關的類型的實例,例如wsgi.file_wrapper返回的「文件包裝器」(請參閱可選)特定於平臺的文件處理)。在通常狀況下,僅接受此處指定或經過PEP 234迭代API訪問的屬性。
根據通用網關接口規範[[2]的](https://www.python.org/dev/pe...,須要環境詞典包含這些CGI環境變量。除非如下值另有說明,不然必須存在如下變量,除非它們的值將爲空字符串,在這種狀況下,能夠將它們省略。[](https://www.python.org/dev/pe...
REQUEST_METHOD
HTTP請求方法,例如「 GET」或「 POST」。這永遠不能是空字符串,所以始終是必需的。
SCRIPT_NAME
與應用程序對象相對應的請求URL的「路徑」的初始部分,以便應用程序知道其虛擬的「位置」。若是應用程序對應於服務器的「根」,則它能夠是一個空字符串。
PATH_INFO
請求URL的其他「路徑」,指定應用程序中請求目標的虛擬「位置」。這可能是一個空字符串,若是請求的URL目標應用程序的根,沒有尾部斜槓。
QUERY_STRING
請求網址中「?」以後的部分(若是有)。可能爲空或缺席。
CONTENT_TYPE
HTTP請求中任何Content-Type字段的內容。可能爲空或缺席。
CONTENT_LENGTH
HTTP請求中任何Content-Length字段的內容。可能爲空或缺席。
SERVER_NAME,SERVER_PORT
與SCRIPT_NAME和PATH_INFO結合使用時,可使用這兩個字符串來完成URL。可是請注意,HTTP_HOST(若是存在)應優先於SERVER_NAME用來重建請求URL。有關更多詳細信息,請參見下面的「URL重構」部分。SERVER_NAME和SERVER_PORT永遠不能爲空字符串,所以始終是必需的。
SERVER_PROTOCOL
客戶端用於發送請求的協議版本。一般,這相似於「 HTTP / 1.0」或「 HTTP / 1.1」,而且應用程序可使用它來肯定如何處理任何HTTP請求標頭。(此變量可能應該稱爲REQUEST_PROTOCOL,由於它表示請求中使用的協議,而且不必定是服務器響應中將使用的協議。可是,爲了與CGI兼容,咱們必須保留現有名稱。)
HTTP_Variables
與客戶端提供的HTTP請求標頭對應的變量(即,名稱以「 HTTP_」開頭的變量)。這些變量的存在與否應與請求中適當的HTTP標頭的存在與否相對應。
服務器或網關應嘗試提供儘量多的其餘CGI變量。另外,若是使用SSL,則服務器或網關還應提供儘量多的Apache SSL環境變量[[5]](https://www.python.org/dev/pe...,例如HTTPS = on和SSL_PROTOCOL。可是請注意,使用除上面列出的變量以外的任何CGI變量的應用程序必然不可移植到不支持相關擴展的Web服務器上。(例如,不發佈文件的Web服務器將沒法提供有意義的DOCUMENT_ROOT或PATH_TRANSLATED。)