本文講述 Flask 0.7 版本的運(yùn)行方式,與舊版本的運(yùn)行方式基本相同,但也有一些細(xì)微的 差別。
建議你在閱讀本文之前,先閱讀應(yīng)用環(huán)境 。
假設(shè)有一個(gè)工具函數(shù),這個(gè)函數(shù)返回用戶重定向的 URL (包括 URL 的 next 參數(shù)、 或 HTTP 推薦和索引頁面):
from flask import request, url_for
def redirect_url():
return request.args.get('next') or \
request.referrer or \
url_for('index')
如上例所示,這個(gè)函數(shù)訪問了請(qǐng)求對(duì)象。如果你在一個(gè)普通的 Python 解釋器中運(yùn)行這個(gè)函數(shù),那么會(huì)看到如下異常:
>>> redirect_url()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'
這是因?yàn)楝F(xiàn)在我們沒有一個(gè)可以訪問的請(qǐng)求。所以我們只能創(chuàng)建一個(gè)請(qǐng)求并綁定到當(dāng)前環(huán)境中。 test_request_context 方法可以創(chuàng)建一個(gè) RequestContext :
>>> ctx = app.test_request_context('/?next=http://example.com/')
這個(gè)環(huán)境有兩種使用方法:一種是使用 with 語句;另一種是調(diào)用 push() 和 pop() 方法:
>>> ctx.push()
現(xiàn)在可以使用請(qǐng)求對(duì)象了:
>>> redirect_url()
u'http://example.com/'
直到你調(diào)用 pop :
>>> ctx.pop()
可以把請(qǐng)求環(huán)境理解為一個(gè)堆棧,可以多次壓入和彈出,可以方便地執(zhí)行一個(gè)像內(nèi)部重定向之類的東西。
關(guān)于在 Python 解釋器中使用請(qǐng)求環(huán)境的更多內(nèi)容參見在 Shell 中使用 Flask 。
如果深入 Flask WSGI 應(yīng)用內(nèi)部,那么會(huì)找到類似如下代碼:
def wsgi_app(self, environ):
with self.request_context(environ):
try:
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
request_context() 方法返回一個(gè)新的 RequestContext 對(duì)象,并且使用 with 語句把這個(gè)對(duì)象綁定到環(huán)境。在 with 語句塊中,在同一個(gè)線程中調(diào)用的所有東西可以訪問全局請(qǐng)求 (flask.request 或其他)。
請(qǐng)求環(huán)境的工作方式就像一個(gè)堆棧,棧頂是當(dāng)前活動(dòng)請(qǐng)求。 push() 把環(huán)境壓入堆棧中,而 pop() 把環(huán)境彈出。彈出的同時(shí),會(huì)執(zhí)行應(yīng)用的 teardown_request() 函數(shù)。
另一件要注意的事情是:請(qǐng)求環(huán)境會(huì)在壓入時(shí)自動(dòng)創(chuàng)建一個(gè)應(yīng)用環(huán)境 。在此之前,應(yīng)用沒有應(yīng)用環(huán)境。
如果在請(qǐng)求處理的過程中發(fā)生錯(cuò)誤,那么 Flask 會(huì)如何處理呢?自 Flask 0.7 版本之后, 處理方式有所改變。這是為了更方便地反映到底發(fā)生了什么情況。新的處理方式非常簡(jiǎn)單:
在每個(gè)請(qǐng)求之前,會(huì)執(zhí)行所有 before_request() 函數(shù)。如果 其中一個(gè)函數(shù)返回一個(gè)響應(yīng),那么其他函數(shù)將不再調(diào)用。但是在任何情況下,這個(gè) 返回值將會(huì)替代視圖的返回值。
如果 before_request() 函數(shù)均沒有響應(yīng),那么就會(huì)進(jìn)行正常的 請(qǐng)求處理,匹配相應(yīng)的視圖,返回響應(yīng)。
接著,視圖的返回值會(huì)轉(zhuǎn)換為一個(gè)實(shí)際的響應(yīng)對(duì)象并交給 after_request() 函數(shù)處理。在處理過程中,這個(gè)對(duì)象可能會(huì)被 替換或修改。
那么如果出錯(cuò)了會(huì)怎么樣?在生產(chǎn)模式下,如果一個(gè)異常未被主要捕獲處理,那么會(huì)調(diào)用 500 內(nèi)部服務(wù)器處理器。在開發(fā)模式下,引發(fā)的異常不再被進(jìn)一步處理,會(huì)提交給 WSGI 服務(wù)器。因此,需要使用交互調(diào)試器來查看調(diào)試信息。
Flask 0.7 版本的重大變化是內(nèi)部服務(wù)器錯(cuò)誤不再由請(qǐng)求后回調(diào)函數(shù)來處理,并且請(qǐng)求后 回調(diào)函數(shù)也不保證一定被執(zhí)行。這樣使得內(nèi)部調(diào)試代碼更整潔、更易懂和更容易定制。
同時(shí)還引入了新的卸載函數(shù),這個(gè)函數(shù)在請(qǐng)求結(jié)束時(shí)一定會(huì)執(zhí)行。
卸載回調(diào)函數(shù)的特殊之處在于其調(diào)用的時(shí)機(jī)是不固定的。嚴(yán)格地說,調(diào)用時(shí)機(jī)取決于其綁定的 RequestContext 對(duì)象的生命周期。當(dāng)請(qǐng)求環(huán)境彈出時(shí)就 會(huì)調(diào)用 teardown_request() 函數(shù)。
請(qǐng)求環(huán)境的生命周期是會(huì)變化的,當(dāng)請(qǐng)求環(huán)境位于測(cè)試客戶端中的 with 語句中或者在 命令行下使用請(qǐng)求環(huán)境時(shí),其生命周期會(huì)被延長。因此知道生命周期是否被延長是很重要的:
with app.test_client() as client:
resp = client.get('/foo')
# 到這里還沒有調(diào)用卸載函數(shù)。即使這時(shí)響應(yīng)已經(jīng)結(jié)束,并且已經(jīng)
# 獲得響應(yīng)對(duì)象,還是不會(huì)調(diào)用卸載函數(shù)。
# 只有到這里才會(huì)調(diào)用卸載函數(shù)。另外,如果另一個(gè)請(qǐng)求在客戶端中被
# 激發(fā),也會(huì)調(diào)用卸載函數(shù)。
在使用命令行時(shí),可以清楚地看到運(yùn)行方式:
>>> app = Flask(__name__)
>>> @app.teardown_request
... def teardown_request(exception=None):
... print 'this runs after request'
...
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ctx.pop()
this runs after request
>>>
記牢記:卸載函數(shù)在任何情況下都會(huì)被執(zhí)行,甚至是在請(qǐng)求預(yù)處理回調(diào)函數(shù)沒有執(zhí)行, 但是發(fā)生異常的情況下。有的測(cè)試系統(tǒng)可能會(huì)臨時(shí)創(chuàng)建一個(gè)請(qǐng)求環(huán)境,但是不執(zhí)行 預(yù)處理器。請(qǐng)正確使用卸載處理器,確保它們不會(huì)執(zhí)行失敗。
部分 Flask 提供的對(duì)象是其他對(duì)象的代理。使用代理的原因是代理對(duì)象共享于不同的 線程,它們?cè)诤笈_(tái)根據(jù)需要把實(shí)際的對(duì)象分配給不同的線程。
多數(shù)情況下,你不需要關(guān)心這個(gè)。但是也有例外,在下列情況有下,知道對(duì)象是一個(gè)代理對(duì)象是有好處的:
想要執(zhí)行真正的實(shí)例檢查的情況。因?yàn)榇韺?duì)象不會(huì)假冒被代理對(duì)象的對(duì)象類型, 因此,必須檢查被代理的實(shí)際對(duì)象(參見下面的 _get_current_object )。 對(duì)象引用非常重要的情況(例如發(fā)送 信號(hào) )。 如果想要訪問被代理的對(duì)象,可以使用 _get_current_object() 方法:
app = current_app._get_current_object()
my_signal.send(app)
不管是否出錯(cuò),在請(qǐng)求結(jié)束時(shí),請(qǐng)求環(huán)境會(huì)被彈出,并且所有相關(guān)聯(lián)的數(shù)據(jù)會(huì)被銷毀。 但是在開發(fā)過程中,可能需要在出現(xiàn)異常時(shí)保留相關(guān)信息。在 Flask 0.6 版本及更早的 版本中,在發(fā)生異常時(shí),請(qǐng)求環(huán)境不會(huì)被彈出,以便于交互調(diào)試器提供重要信息。
自 Flask 0.7 版本開始,可以通過設(shè)置 PRESERVE_CONTEXT_ON_EXCEPTION 配置變量來更好地控制環(huán)境的保存。缺省情況下,這個(gè)配置變更與 DEBUG 變更關(guān)聯(lián)。如果在調(diào)試模式下,那么環(huán)境會(huì)被保留,而在生產(chǎn)模式下則不保留。
不要在生產(chǎn)環(huán)境下強(qiáng)制激活 PRESERVE_CONTEXT_ON_EXCEPTION ,因?yàn)檫@會(huì)在出現(xiàn)異常 時(shí)導(dǎo)致應(yīng)用內(nèi)存溢出。但是在調(diào)試模式下使用這個(gè)變更是十分有用的,你可以獲得在生產(chǎn)模式下出錯(cuò)時(shí)的環(huán)境。
? Copyright 2013, Armin Ronacher. Created using Sphinx.