從一個(gè)簡單的 Flask 示例開始。
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
print(request.headers)
return 'Hello World!'
if __name__ == '__main__':
app.run()
調(diào)試程序叽唱,在客戶端輸入 http://localhost:5000煞茫,瀏覽器顯示 Hello World,在 IDE 中顯示了請(qǐng)求頭的信息地消。看起來畏妖,request 對(duì)象像一個(gè)全局變量脉执,導(dǎo)入后就可以直接使用。那么戒劫,我們將代碼變一個(gè)位置:
if __name__ == '__main__':
print(request.headers)
app.run()
這個(gè)時(shí)候半夷,程序出錯(cuò)了,拋出如下面所示的異常:
RuntimeError: Working outside of request context.
意思是 request 不在 context 之中迅细。為什么在視圖函數(shù)中可以直接使用 request巫橄,而在 app.run() 代碼之前就不行呢?這就引出了一個(gè)請(qǐng)求上下文 (request context) 的概念茵典。我們知道湘换,web 程序啟動(dòng)后,在同一時(shí)間,可能來自多個(gè)客戶端的請(qǐng)求彩倚,這些請(qǐng)求是不同的筹我,有著各自不同的請(qǐng)求報(bào)文。服務(wù)器對(duì)來自不同客戶端的請(qǐng)求署恍,必須能夠單獨(dú)進(jìn)行處理崎溃,互不干擾。Flask 用請(qǐng)求上下文 (request context) 來實(shí)現(xiàn)對(duì)來自不同客戶端的請(qǐng)求能獨(dú)立處理盯质,又讓開發(fā)人員在編寫代碼的時(shí)候袁串,request 像一個(gè)全局變量。
那么呼巷,這個(gè) request 是怎么來的呢囱修?為什么在導(dǎo)入后就可以直接使用?本文將從代碼的角度王悍,對(duì) Flask 背后的機(jī)制進(jìn)行剖析破镰。結(jié)合在 PyCharm 中對(duì)代碼的調(diào)試和 Flask 源代碼功能進(jìn)行講解。在 app.run() 語句打一個(gè)斷點(diǎn):
調(diào)試到該語句压储,變量區(qū)域顯示 request 是一個(gè) LocalProxy
對(duì)象鲜漩,此時(shí) request 的狀態(tài)為 unbound,并沒有跟實(shí)際來自客戶端的請(qǐng)求綁定集惋。
接著按 F8孕似,我們看到,F(xiàn)lask 已經(jīng)啟動(dòng)刮刑,等待客戶端請(qǐng)求喉祭。先來看看截止這個(gè)階段與 request 相關(guān)的代碼。從 from flask import request
語句跳轉(zhuǎn)到源代碼雷绢。代碼位于 globals.py 文件中泛烙,主要的代碼如下:
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
因?yàn)橛?import 語句,所以 globals.py 的代碼被加載翘紊,request 被實(shí)例化蔽氨。
接下來我們看看 app.run()
做了什么。如果調(diào)試該代碼帆疟,就進(jìn)入 Flask 類的 run()
方法孵滞。去掉 run()
方法無關(guān)的代碼,該方法可以簡化為:
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
# 去掉無關(guān)代碼
# ...
run_simple(host, port, self, **options)
這個(gè) run_simple
方法是 werkzeug.serving
模塊提供的方法鸯匹,作用是啟動(dòng)一個(gè)服務(wù)器。我們可以自己寫一段簡單代碼來體驗(yàn)一下 werkzeug 實(shí)現(xiàn)的這個(gè)簡單服務(wù)器:
from werkzeug.wrappers import Response, Request
from werkzeug.serving import run_simple
@Request.application
def simple_app(req):
return Response("Hello from werkzeug!")
run_simple(hostname="127.0.0.1", port=9000, application=simple_app)
Flask 的代碼復(fù)雜得多泄伪,但機(jī)制相同殴蓬。
接下來,看看 Flask 如何處理來自客戶端的請(qǐng)求。當(dāng)客戶端發(fā)起請(qǐng)求后染厅,web server 將請(qǐng)求轉(zhuǎn)交給后端 Flask application痘绎,此時(shí)就調(diào)用 Flask 的 __call__()
方法。Flask __call__()
方法的代碼如下:
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
所以肖粮,對(duì)來自客戶端的請(qǐng)求孤页,會(huì)自動(dòng)調(diào)用 __call__()
方法,啟動(dòng) wsgi_app涩馆。下面的代碼是 request 機(jī)制的核心:
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
與 request 相關(guān)的有兩句代碼:
ctx = self.request_context(environ) # 創(chuàng)建RequestContext實(shí)例行施,其中包含request
ctx.push() # RequestContext對(duì)象入棧
通過查看代碼和調(diào)試的方式,運(yùn)行機(jī)制不難理解魂那。首先蛾号,request_context
是一個(gè)函數(shù),返回 RequestContext
:
def request_context(self, environ):
return RequestContext(self, environ)
RequestContext
類的 __init__()
方法根據(jù) environ (環(huán)境變量) 構(gòu)建請(qǐng)求涯雅。__enter__
和 __exit__
方法實(shí)現(xiàn)了上下文管理鲜结。如果不關(guān)注更細(xì)節(jié)東西,至此基本基本可以理解請(qǐng)求的機(jī)制活逆。
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
# 其他代碼略
request_class 聲明在 Flask app.py 代碼中精刷,其實(shí)就是 werkzeug.wrappers.Request
。
總結(jié):Flask 通過請(qǐng)求上下文蔗候,自動(dòng)管理每個(gè) HTTP 請(qǐng)求的生命周期怒允。當(dāng)接收到客戶端的請(qǐng)求,創(chuàng)建一個(gè)新的 request 對(duì)象琴庵,包含請(qǐng)求報(bào)文信息误算。當(dāng)請(qǐng)求結(jié)束時(shí),自動(dòng)銷毀請(qǐng)求迷殿。