淺析WSGI

首先,什麼是WSGI?

WSGI, 全稱爲 Web Server Gateway Interfacepython

它不是什麼框架,它是一個規範,可是做爲一個規範,它實際上並無官方標準。
這裏要說明一下,PEP-0333並非用來規範WSGI用得,而是PEP-0333提出應該有那麼一個東西用來處理web服務器和應用服務器之間的關係,所以產生了WSGI。nginx

其次,爲何PEP-0333以爲須要這個規範呢?

由於,就目前來看,Web服務通常按照以下方式部署:web

  1. 先部署一個web服務器(Apache etc.)用於處理協議層的業務,好比,單臺物理機多服務(多域名或者多端口)、負載均衡等。apache

  2. 而後部署一個應用服務器(Django etc.),它用於處理具體業務方面的事情,好比,滴滴打車的應用服務器就會處理打車訂單CRUD,處理完成以後呢再返回給web服務器,web服務器收到響應以後再返回給客戶端。服務器

WSGI是如何工做的?

經過上文,咱們能夠了解到WSGI無非作了兩件事:app

  1. 讓Web服務器知道如何調用Python應用程序並把從客戶端來的請求拿過來。負載均衡

  2. 讓Python應用程序知道客戶端的具體請求是什麼,以及如何返回結果給Web服務器並幫助Python應用程序把計算後的結果返回給Web服務器。框架

也就是說,WSGI是鏈接Web服務器和應用服務器的橋樑。測試

目前實現的WSGI中,有兩個角色,分別是Server/Gatewayapplication/framework設計

當請求來臨的時候,server調用application,而後application把結果返回給server

那麼,server是如何調用application的?

接下來,server須要知道去哪可以找到application。實現這一邏輯,須要在server指定一個Python模塊(具體在server的哪一個位置保存這一路徑,那就得根據具體server類型來選擇了,如apache或nginx)該模塊必須包含一個名稱爲application的可調用對象,這個對象的形式以下:

PEP-0333中指出了,一個WSGI的application角色,應該是一個可調用對象:

def application(environ, start_response)

其中,environ是一個包含了關於此次HTTP請求信息的字典,start_response是一個可調用對象,用於在application中執行,用以在返回響應內容前設置響應的狀態碼和響應頭,同時也意味着告訴serverapplication要開始返回http的body了。這兩個就是server調用application時候須要傳遞的全部參數了。

那麼,咱們還須要再說一下,environstart_response()是須要在server端的生成和定義的

有了這兩個參數,application就能知道用戶請求的是什麼資源,請求中帶了什麼數據,結果該如何返回給server等等。其中,environ包含了一些符合CGI規範的環境變量和WSGI規範新添加的變量,此外還可能有一些系統變量及Web服務器相關的環境變量。start_response是一個可調用對象,它包含了一個表示HTTP響應狀態的字符串和一個HTTP響應headers的列表以及一個用於出錯返回的信息,具體參數包含及詳情請點這裏。

下面是一個完整的application demo,最終返回的body也能夠是一個可迭代對象,這樣server也能夠經過遍歷這個對象來拼接成body。

def application(environ, start_response):
    start_response("200 OK", [("Content-type", "text/plain")])
    return ["Hello World!",]

看上去不錯,那麼咱們該如何調用呢?

有幾個服務器組件也可以運行的WSGI應用程序,可是對於簡單的測試目的,咱們可使用包含在Python的標準庫參考實現,就像下面那樣:

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8080, application)
    server.serve_forever()

好了,還記得咱們剛纔提到的最終返回的body也能夠是一個可迭代對象嗎?在python中,建立一個迭代器的簡單辦法就是使用一個生成器,好比,咱們有一個用於靜態文件的服務,咱們能夠寫一個生成器,讓它一次生成一個固定大小的文件塊,這樣咱們就能夠隨時一次只存儲一個文件塊了,讓咱們來試試吧:

def send_file(file_path, size):
    with open(file_path) as f:
        block = f.read(BLOCK_SIZE)
        while block:
            yield block
            block = f.read(BLOCK_SIZE)

對應的 WSGI application 部分以下:

size = os.path.getsize(file_path)
headers = [
    ("Content-type", mimetype),
    ("Content-length", str(size)),
]
start_response("200 OK", headers)
return send_file(file_path, size)

注意,send_file就是上文中所指的可迭代對象。

WSGI中間件

看得出來,WSGI應用結構很是簡潔,只需指定一個可調用的識別標記,這使得WSGI應用很容易實現調用其餘框架,無非就是修改到來的請求或者修改發出的響應,固然也能夠二者都有。那麼,接下來看看demo:

class Filter(object):
    def __init__(self, application):
        self.application = application
        
    def __call__(self, environ, start_response):
        # Do something here to modify request
        pass
        
        # Call the wrapped application
        app_iter = self.application(environ, 
                                    self._sr_callback(start_response))
        
        # Do something to modify the response body
        pass
        
        # Return modified response
        return app_iter
        
    def _sr_callback(self, start_response):
        def callback(status, headers, exc_info=None):
            # Do something to modify the response status or headers
            pass
        
            # Call upstream start_response
            start_response(status, headers, exc_info)
        return callback

像這樣應用一般被稱爲Middleware applicationsFilter Filter能夠被鏈接在一塊兒,由此產生的鏈一般被稱爲pipeline

最後呢,我想說明一下,WSGI是故意設計成最小的Web服務器實現輕鬆的應用,以便被更多的人採用。可是,幾乎沒有人真的喜歡直接操做environ變量,也幾乎沒有人喜歡用start_response這麼詭異的邏輯,雖然WSGI提供的API易於實現,但這不表明它的語義讓人滿意。也正是由於這一緣由,幾乎每個應用程序或者web框架都把environstart_response封裝成了語義更完善容錯率更高的request和response對象。

Webob就是request和response對象的規範實現之一,它使得WSGI更容易和更滿意地被你們使用。

你能夠在這裏找到Webob的官方文檔,固然,下次有機會我也會簡單的說明一下Webob到底有多方便。

相關文章
相關標籤/搜索