摘要:中有一個非常重要的概念每個應用都是一個可調用的對象。它規定了的接口,會調用,并傳給它兩個參數包含了請求的所有信息,是處理完之后需要調用的函數,參數是狀態碼響應頭部還有錯誤信息。一般來說,嵌套的最后一層是業務應用,中間就是。
文章屬于作者原創,原文發布在個人博客。
WSGI所有的 python web 框架都要遵循 WSGI 協議,如果對 WSGI 不清楚,可以查看我之前的介紹文章。
在這里還是要簡單回顧一下 WSGI 的核心概念。
WSGI 中有一個非常重要的概念:每個 python web 應用都是一個可調用(callable)的對象。在 flask 中,這個對象就是 app = Flask(__name__) 創建出來的 app,就是下圖中的綠色 Application 部分。要運行 web 應用,必須有 web server,比如我們熟悉的 apache、nginx ,或者 python 中的 gunicorn ,我們下面要講到的 werkzeug 提供的 WSGIServer,它們是下圖的黃色 Server 部分。
NOTE: 圖片來源。
Server 和 Application 之間怎么通信,就是 WSGI 的功能。它規定了 app(environ, start_response) 的接口,server 會調用 application,并傳給它兩個參數:environ 包含了請求的所有信息,start_response 是 application 處理完之后需要調用的函數,參數是狀態碼、響應頭部還有錯誤信息。
WSGI application 非常重要的特點是:它是可以嵌套的。換句話說,我可以寫個 application,它做的事情就是調用另外一個 application,然后再返回(類似一個 proxy)。一般來說,嵌套的最后一層是業務應用,中間就是 middleware。這樣的好處是,可以解耦業務邏輯和其他功能,比如限流、認證、序列化等都實現成不同的中間層,不同的中間層和業務邏輯是不相關的,可以獨立維護;而且用戶也可以動態地組合不同的中間層來滿足不同的需求。
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() ,這個方法的代碼如下:
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 要做的就是找到我們的處理函數,并返回調用的結果,也就是路由的過程。我們下一篇文章來講!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38372.html
摘要:我們知道響應分為三個部分狀態欄版本狀態碼和說明頭部以冒號隔開的字符對,用于各種控制和協商服務端返回的數據。 這是 flask 源碼解析系列文章的其中一篇,本系列所有文章列表: flask 源碼解析:簡介 flask 源碼解析:應用啟動流程 flask 源碼解析:路由 flask 源碼解析:上下文 flask 源碼解析:請求 flask 源碼解析:響應 response 簡介 在 f...
摘要:簡介官網上對它的定位是一個微開發框架。另外一個必須理解的概念是,簡單來說就是一套和框架應用之間的協議。功能比較豐富,支持解析自動防止攻擊繼承變量過濾器流程邏輯支持代碼邏輯集成等等。那么,從下一篇文章,我們就正式開始源碼之旅了 文章屬于作者原創,原文發布在個人博客。 flask 簡介 Flask 官網上對它的定位是一個微 python web 開發框架。 Flask is a micro...
摘要:可以看到,雖然是同樣的請求數據,在不同的階段和不同組件看來,是完全不同的形式。請求還有一個不那么明顯的特性它不能被應用修改,應用只能讀取請求的數據。 這是 flask 源碼解析系列文章的其中一篇,本系列所有文章列表: flask 源碼解析:簡介 flask 源碼解析:應用啟動流程 flask 源碼解析:路由 flask 源碼解析:上下文 flask 源碼解析:請求 flask 源碼解...
摘要:但是這些對象和全局變量不同的是它們必須是動態的,因為在多線程或者多協程的情況下,每個線程或者協程獲取的都是自己獨特的對象,不會互相干擾。中有兩種上下文和。就是實現了類似的效果多線程或者多協程情況下全局變量的隔離效果。 這是 flask 源碼解析系列文章的其中一篇,本系列所有文章列表: flask 源碼解析:簡介 flask 源碼解析:應用啟動流程 flask 源碼解析:路由 flas...
閱讀 4293·2021-09-24 09:47
閱讀 1184·2021-09-03 10:33
閱讀 2063·2019-08-30 11:13
閱讀 1029·2019-08-30 10:49
閱讀 1753·2019-08-29 16:13
閱讀 2046·2019-08-29 11:28
閱讀 3089·2019-08-26 13:31
閱讀 3631·2019-08-23 17:14