文章屬於做者原創,原文發佈在我的博客。python
全部的 python web 框架都要遵循 WSGI 協議,若是對 WSGI 不清楚,能夠查看我以前的介紹文章。nginx
在這裏仍是要簡單回顧一下 WSGI 的核心概念。web
WSGI 中有一個很是重要的概念:每一個 python web 應用都是一個可調用(callable)的對象。在 flask 中,這個對象就是 app = Flask(__name__)
建立出來的 app
,就是下圖中的綠色 Application 部分。要運行 web 應用,必須有 web server,好比咱們熟悉的 apache、nginx ,或者 python 中的 gunicorn ,咱們下面要講到的 werkzeug
提供的 WSGIServer
,它們是下圖的黃色 Server 部分。apache
NOTE: 圖片來源。flask
Server 和 Application 之間怎麼通訊,就是 WSGI 的功能。它規定了 app(environ, start_response)
的接口,server 會調用 application,並傳給它兩個參數:environ
包含了請求的全部信息,start_response
是 application 處理完以後須要調用的函數,參數是狀態碼、響應頭部還有錯誤信息。服務器
WSGI application 很是重要的特色是:它是能夠嵌套的。換句話說,我能夠寫個 application,它作的事情就是調用另一個 application,而後再返回(相似一個 proxy)。通常來講,嵌套的最後一層是業務應用,中間就是 middleware。這樣的好處是,能夠解耦業務邏輯和其餘功能,好比限流、認證、序列化等都實現成不一樣的中間層,不一樣的中間層和業務邏輯是不相關的,能夠獨立維護;並且用戶也能夠動態地組合不一樣的中間層來知足不一樣的需求。app
WSGI 的內容就講這麼多,咱們來看看 flask 的 hello world 應用:框架
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()
這裏的 app = Flask(__name__)
就是上面提到的 Application 部分,可是咱們並無看到 Server 的部分,那麼它必定是隱藏到 app.run()
內部某個地方了。函數
應用啓動的代碼是 app.run()
,這個方法的代碼以下:post
def run(self, host=None, port=None, debug=None, **options): """Runs the application on a local development server.""" from werkzeug.serving import run_simple # 若是host 和 port 沒有指定,設置 host 和 port 的默認值 127.0.0.1 和 5000 if host is None: host = '127.0.0.1' if port is None: server_name = self.config['SERVER_NAME'] if server_name and ':' in server_name: port = int(server_name.rsplit(':', 1)[1]) else: port = 5000 # 調用 werkzeug.serving 模塊的 run_simple 函數,傳入收到的參數 # 注意第三個參數傳進去的是 self,也就是要執行的 web application try: run_simple(host, port, self, **options) finally: self._got_first_request = False
NOTE:爲了閱讀方便,我刪除了註釋和不相干的部分,下面全部的代碼都會作相似的處理,再也不贅述。
這個方法的內容很是簡單:處理一下參數,而後調用 werkzeug
的 run_simple
。須要注意的是:run_simple
的第三個參數是 self
,也就是咱們建立的 Flask()
application。由於 WSGI server 不是文章的重點,因此咱們就不深刻講解了。如今只須要知道它的功能就行:監聽在指定的端口,收到 HTTP 請求的時候解析爲 WSGI 格式,而後調用 app
去執行處理的邏輯。對應的執行邏輯在 werkzeug.serving:WSGIRequestHandler
的 run_wsgi
中有這麼一段代碼:
def execute(app): application_iter = app(environ, start_response) try: for data in application_iter: write(data) if not headers_sent: write(b'') finally: if hasattr(application_iter, 'close'): application_iter.close() application_iter = None
能夠看到 application_iter = app(environ, start_response)
就是調用代碼獲取結果的地方。
要調用 app
實例,那麼它就須要定義了 __call__
方法,咱們找到 flask.app:Flask
對應的內容:
def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response) def wsgi_app(self, environ, start_response): """The actual WSGI application. """ # 建立請求上下文,並把它壓棧。這個在後面會詳細解釋 ctx = self.request_context(environ) ctx.push() error = None try: try: # 正確的請求處理路徑,會經過路由找到對應的處理函數 response = self.full_dispatch_request() except Exception as e: # 錯誤處理,默認是 InternalServerError 錯誤處理函數,客戶端會看到服務器 500 異常 error = e response = self.handle_exception(e) return response(environ, start_response) finally: if self.should_ignore_error(error): error = None # 無論處理是否發生異常,都須要把棧中的請求 pop 出來 ctx.auto_pop(error)
上面這段代碼只有一個目的:找處處理函數,而後調用它。除了異常處理以外,咱們還看到了 context
相關的內容(開始有 ctx.push()
,最後有 ctx.auto_pop()
的邏輯),它並不影響咱們的理解,如今能夠先不用管,後面會有一篇文章專門介紹。
繼續日後看,full_dsipatch_request
的代碼以下:
def full_dispatch_request(self): """Dispatches the request and on top of that performs request pre and postprocessing as well as HTTP exception catching and error handling. """ self.try_trigger_before_first_request_functions() try: request_started.send(self) rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv)
這段代碼最核心的內容是 dispatch_request
,加上請求的 hooks 處理和錯誤處理的內容。
NOTE:self.dispatch_request()
返回的是處理函數的返回結果(好比 hello world 例子中返回的字符串),finalize_request
會把它轉換成 Response
對象。
在 dispatch_request
以前咱們看到 preprocess_request
,以後看到 finalize_request
,它們裏面包括了請求處理以前和處理以後的不少 hooks 。這些 hooks 包括:
第一次請求處理以前的 hook 函數,經過 before_first_request
定義
每一個請求處理以前的 hook 函數,經過 before_request
定義
每一個請求正常處理以後的 hook 函數,經過 after_request
定義
無論請求是否異常都要執行的 teardown_request
hook 函數
dispatch_request
要作的就是找到咱們的處理函數,並返回調用的結果,也就是路由的過程。咱們下一篇文章來說!