學(xué)習(xí)一樣?xùn)|西不能只停留在表面,我們要探索其中的細(xì)節(jié)魁亦,學(xué)習(xí)作者的編程思想渔隶,這樣才能更進(jìn)一步。
關(guān)于WSGI
WSGI(全稱(chēng)Web Server Gateway Interface
),是為 Python 語(yǔ)言定義的Web服務(wù)器
和Web應(yīng)用程序
之間的一種簡(jiǎn)單而通用的接口
间唉,它封裝了接受HTTP請(qǐng)求
绞灼、解析HTTP請(qǐng)求
、發(fā)送HTTP
呈野,響應(yīng)
等等的這些底層的代碼和操作低矮,使開(kāi)發(fā)者可以高效的編寫(xiě)Web應(yīng)用。
一個(gè)簡(jiǎn)單的使用WSGI的App例子:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, I Am WSGI!</h1>']
-
environ
: 一個(gè)包含全部HTTP請(qǐng)求信息的字典被冒,由WSGI Server
解包HTTP請(qǐng)求生成军掂。 -
start_response
: 一個(gè)WSGI Server
提供的函數(shù),調(diào)用可以發(fā)送響應(yīng)的狀態(tài)碼和HTTP
報(bào)文頭昨悼, 函數(shù)在返回前必須調(diào)用一次start_response()
蝗锥。 -
application()
應(yīng)當(dāng)返回一個(gè)可以迭代的對(duì)象(HTTP正文)。 -
application()
函數(shù)由WSGI Server
直接調(diào)用和提供參數(shù)率触。 - Python內(nèi)置了一個(gè)
WSGIREF
的WSGI Server
终议,不過(guò)性能不是很好,一般只用在開(kāi)發(fā)環(huán)境葱蝗⊙ㄕ牛可以選擇其他的如Gunicorn
。
Flask的上下文對(duì)象
Flask有兩種Context
(上下文)两曼,分別是
-
RequestContext
請(qǐng)求上下文 -
Request
請(qǐng)求的對(duì)象皂甘,封裝了Http請(qǐng)求(environ
)的內(nèi)容 -
Session
根據(jù)請(qǐng)求中的cookie,重新載入該訪(fǎng)問(wèn)者相關(guān)的會(huì)話(huà)信息悼凑。 -
AppContext
程序上下文 -
g
處理請(qǐng)求時(shí)用作臨時(shí)存儲(chǔ)的對(duì)象偿枕。每次請(qǐng)求都會(huì)重設(shè)這個(gè)變量 -
current_app
當(dāng)前激活程序的程序?qū)嵗?/li>
生命周期:
-
current_app
的生命周期最長(zhǎng),只要當(dāng)前程序?qū)嵗€在運(yùn)行佛析,都不會(huì)失效益老。 -
Request
和g
的生命周期為一次請(qǐng)求期間,當(dāng)請(qǐng)求處理完成后寸莫,生命周期也就完結(jié)了 -
Session
就是傳統(tǒng)意義上的session了捺萌。只要它還未失效(用戶(hù)未關(guān)閉瀏覽器、沒(méi)有超過(guò)設(shè)定的失效時(shí)間)膘茎,那么不同的請(qǐng)求會(huì)共用同樣的session桃纯。
Flask處理流程
- 第一步:創(chuàng)建上下文
Flask根據(jù)WSGI Server封裝的請(qǐng)求等的信息(environ
)新建RequestContext對(duì)象
和AppContext對(duì)象
# 聲明對(duì)象
# LocalStack LocalProxy 都由Werkzeug提供
# 我們不深究他的細(xì)節(jié),那又是另外一個(gè)故事了披坏,我們只需知道他的作用就行了
# LocalStack 是棧結(jié)構(gòu)态坦,可以將對(duì)象推入、彈出
# 也可以快速拿到棧頂對(duì)象棒拂。當(dāng)然伞梯,所有的修改都只在本線(xiàn)程可見(jiàn)玫氢。
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# 如果調(diào)用一個(gè)LocalStack實(shí)例, 能返回一個(gè) LocalProxy 對(duì)象
# 這個(gè)對(duì)象始終指向 這個(gè)LocalStack實(shí)例的棧頂元素谜诫。
# 如果棧頂元素不存在漾峡,訪(fǎng)問(wèn)這個(gè) LocalProxy 的時(shí)候會(huì)拋出 RuntimeError異常
# LocalProxy對(duì)象你只需暫時(shí)理解為棧里面的元素即可了
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'))
# RequestContext
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
#AppContext
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
self._refcnt = 0
這里需要注意的是,RequestContext
在初始化的時(shí)候喻旷,當(dāng)前Flask的實(shí)例作為參數(shù)被傳進(jìn)來(lái)生逸。雖然每次的請(qǐng)求處理都會(huì)創(chuàng)建一個(gè)RequestContext對(duì)象,但是每一次傳入的app參數(shù)卻是同一個(gè)且预。通過(guò)這個(gè)機(jī)制槽袄,可以使得:
由同一個(gè)Flask實(shí)例所創(chuàng)建的
RequestContext
,其成員變量app都是同一個(gè)Flask實(shí)例對(duì)象 锋谐。實(shí)現(xiàn)了多個(gè)RequestContext
對(duì)應(yīng)同一個(gè)current_app
的目的遍尺。
- 第二步:入棧
將RequestContext
對(duì)象push進(jìn)_request_ctx_stack
里面。在這次請(qǐng)求期間涮拗,訪(fǎng)問(wèn)request對(duì)象
狮鸭,session對(duì)象
將指向這個(gè)棧的棧頂元素
class RequestContext(object):
def push(self):
....
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
AppContext對(duì)象push進(jìn)_app_ctx_stack
里面。在這次請(qǐng)求期間多搀,訪(fǎng)問(wèn)g
對(duì)象將指向這個(gè)棧的棧頂元素
class AppContext(object):
def push(self):
....
_request_ctx_stack.push(self)
第三步:請(qǐng)求分發(fā)
response = self.full_dispatch_request()
Flask將調(diào)用full_dispatch_request
函數(shù)進(jìn)行請(qǐng)求的分發(fā),之所以不用給參數(shù)灾部,是因?yàn)槲覀兛梢酝ㄟ^(guò)request
對(duì)象獲得這次請(qǐng)求的信息康铭。full_dispatch_request
將根據(jù)請(qǐng)求的url找到對(duì)應(yīng)的藍(lán)本里面的視圖函數(shù),并生成一個(gè)response
對(duì)象赌髓。注意的是从藤,在請(qǐng)求之外的時(shí)間,訪(fǎng)問(wèn)request對(duì)象是無(wú)效的锁蠕,因?yàn)閞equest對(duì)象依賴(lài)請(qǐng)求期間的_request_ctx_stack
棧夷野。第四步:上下文對(duì)象出棧
這次HTTP的響應(yīng)已經(jīng)生成了,就不需要兩個(gè)上下文對(duì)象了荣倾。分別將兩個(gè)上下文對(duì)象出棧悯搔,為下一次的HTTP請(qǐng)求做出準(zhǔn)備。第五步:響應(yīng)WSGI
調(diào)用Response對(duì)象舌仍,向WSGI Server返回其結(jié)果作為HTTP正文妒貌。Response對(duì)象是一個(gè) 可調(diào)用對(duì)象,當(dāng)調(diào)用發(fā)生時(shí)铸豁,將首先執(zhí)行WSGI服務(wù)器傳入的start_response()函數(shù) 發(fā)送狀態(tài)碼和HTTP報(bào)文頭灌曙。
最后附上Flask處理請(qǐng)求的wsgi_app
函數(shù)
# environ: WSGI Server封裝的HTTP請(qǐng)求信息
# start_response: WSGI Server提供的函數(shù),調(diào)用可以發(fā)送狀態(tài)碼和HTTP報(bào)文頭
def wsgi_app(self, environ, start_response):
# 根據(jù)environ創(chuàng)建上下文
ctx = self.request_context(environ)
# 把當(dāng)前的request context,app context綁定到當(dāng)前的context
ctx.push()
error = None
try:
try:
#根據(jù)請(qǐng)求的URL节芥,分發(fā)請(qǐng)求在刺,經(jīng)過(guò)視圖函數(shù)處理后返回響應(yīng)對(duì)象
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 最后出棧
ctx.auto_pop(error)
參考資料
匯智網(wǎng):Flask框架-上下文對(duì)象 :Flask核心機(jī)制