轉載於:http://cizixs.com/2017/01/11/flask-insight-start-process
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
要做的就是找到我們的處理函數,並返回調用的結果,也就是路由的過程。我們下一篇文章來講!