鍍金池/ 教程/ Python/ 請求環(huán)境
應(yīng)用環(huán)境
配置管理
大型應(yīng)用
可插撥視圖
Flask 方案
在 Shell 中使用 Flask
針對高級程序員的前言
使用藍(lán)圖的模塊化應(yīng)用
部署方式
信號
排除應(yīng)用錯誤
模板
請求環(huán)境
掌握應(yīng)用錯誤
測試 Flask 應(yīng)用
前言
教程
安裝
快速上手
Flask 擴(kuò)展

請求環(huán)境

本文講述 Flask 0.7 版本的運行方式,與舊版本的運行方式基本相同,但也有一些細(xì)微的 差別。

建議你在閱讀本文之前,先閱讀應(yīng)用環(huán)境 。

深入本地環(huán)境

假設(shè)有一個工具函數(shù),這個函數(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')

如上例所示,這個函數(shù)訪問了請求對象。如果你在一個普通的 Python 解釋器中運行這個函數(shù),那么會看到如下異常:

>>> redirect_url()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'

這是因為現(xiàn)在我們沒有一個可以訪問的請求。所以我們只能創(chuàng)建一個請求并綁定到當(dāng)前環(huán)境中。 test_request_context 方法可以創(chuàng)建一個 RequestContext :

>>> ctx = app.test_request_context('/?next=http://example.com/')

這個環(huán)境有兩種使用方法:一種是使用 with 語句;另一種是調(diào)用 push() 和 pop() 方法:

>>> ctx.push()

現(xiàn)在可以使用請求對象了:

>>> redirect_url()
u'http://example.com/'

直到你調(diào)用 pop :

>>> ctx.pop()

可以把請求環(huán)境理解為一個堆棧,可以多次壓入和彈出,可以方便地執(zhí)行一個像內(nèi)部重定向之類的東西。

關(guān)于在 Python 解釋器中使用請求環(huán)境的更多內(nèi)容參見在 Shell 中使用 Flask 。

環(huán)境的工作原理

如果深入 Flask WSGI 應(yīng)用內(nèi)部,那么會找到類似如下代碼:

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() 方法返回一個新的 RequestContext 對象,并且使用 with 語句把這個對象綁定到環(huán)境。在 with 語句塊中,在同一個線程中調(diào)用的所有東西可以訪問全局請求 (flask.request 或其他)。

請求環(huán)境的工作方式就像一個堆棧,棧頂是當(dāng)前活動請求。 push() 把環(huán)境壓入堆棧中,而 pop() 把環(huán)境彈出。彈出的同時,會執(zhí)行應(yīng)用的 teardown_request() 函數(shù)。

另一件要注意的事情是:請求環(huán)境會在壓入時自動創(chuàng)建一個應(yīng)用環(huán)境 。在此之前,應(yīng)用沒有應(yīng)用環(huán)境。

回調(diào)和錯誤處理

如果在請求處理的過程中發(fā)生錯誤,那么 Flask 會如何處理呢?自 Flask 0.7 版本之后, 處理方式有所改變。這是為了更方便地反映到底發(fā)生了什么情況。新的處理方式非常簡單:

  1. 在每個請求之前,會執(zhí)行所有 before_request() 函數(shù)。如果 其中一個函數(shù)返回一個響應(yīng),那么其他函數(shù)將不再調(diào)用。但是在任何情況下,這個 返回值將會替代視圖的返回值。

  2. 如果 before_request() 函數(shù)均沒有響應(yīng),那么就會進(jìn)行正常的 請求處理,匹配相應(yīng)的視圖,返回響應(yīng)。

  3. 接著,視圖的返回值會轉(zhuǎn)換為一個實際的響應(yīng)對象并交給 after_request() 函數(shù)處理。在處理過程中,這個對象可能會被 替換或修改。

  4. 請求處理的最后一環(huán)是執(zhí)行 teardown_request() 函數(shù)。這類 函數(shù)在任何情況下都會被執(zhí)行,甚至是在發(fā)生未處理異?;蛘埱箢A(yù)處理器沒有執(zhí)行( 例如在測試環(huán)境下,有時不想執(zhí)行)的情況下。

那么如果出錯了會怎么樣?在生產(chǎn)模式下,如果一個異常未被主要捕獲處理,那么會調(diào)用 500 內(nèi)部服務(wù)器處理器。在開發(fā)模式下,引發(fā)的異常不再被進(jìn)一步處理,會提交給 WSGI 服務(wù)器。因此,需要使用交互調(diào)試器來查看調(diào)試信息。

Flask 0.7 版本的重大變化是內(nèi)部服務(wù)器錯誤不再由請求后回調(diào)函數(shù)來處理,并且請求后 回調(diào)函數(shù)也不保證一定被執(zhí)行。這樣使得內(nèi)部調(diào)試代碼更整潔、更易懂和更容易定制。

同時還引入了新的卸載函數(shù),這個函數(shù)在請求結(jié)束時一定會執(zhí)行。

卸載回調(diào)函數(shù)

卸載回調(diào)函數(shù)的特殊之處在于其調(diào)用的時機(jī)是不固定的。嚴(yán)格地說,調(diào)用時機(jī)取決于其綁定的 RequestContext 對象的生命周期。當(dāng)請求環(huán)境彈出時就 會調(diào)用 teardown_request() 函數(shù)。

請求環(huán)境的生命周期是會變化的,當(dāng)請求環(huán)境位于測試客戶端中的 with 語句中或者在 命令行下使用請求環(huán)境時,其生命周期會被延長。因此知道生命周期是否被延長是很重要的:

with app.test_client() as client:
    resp = client.get('/foo')
    # 到這里還沒有調(diào)用卸載函數(shù)。即使這時響應(yīng)已經(jīng)結(jié)束,并且已經(jīng)
    # 獲得響應(yīng)對象,還是不會調(diào)用卸載函數(shù)。

# 只有到這里才會調(diào)用卸載函數(shù)。另外,如果另一個請求在客戶端中被
# 激發(fā),也會調(diào)用卸載函數(shù)。

在使用命令行時,可以清楚地看到運行方式:

>>> 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ù)在任何情況下都會被執(zhí)行,甚至是在請求預(yù)處理回調(diào)函數(shù)沒有執(zhí)行, 但是發(fā)生異常的情況下。有的測試系統(tǒng)可能會臨時創(chuàng)建一個請求環(huán)境,但是不執(zhí)行 預(yù)處理器。請正確使用卸載處理器,確保它們不會執(zhí)行失敗。

關(guān)于代理

部分 Flask 提供的對象是其他對象的代理。使用代理的原因是代理對象共享于不同的 線程,它們在后臺根據(jù)需要把實際的對象分配給不同的線程。

多數(shù)情況下,你不需要關(guān)心這個。但是也有例外,在下列情況有下,知道對象是一個代理對象是有好處的:

想要執(zhí)行真正的實例檢查的情況。因為代理對象不會假冒被代理對象的對象類型, 因此,必須檢查被代理的實際對象(參見下面的 _get_current_object )。 對象引用非常重要的情況(例如發(fā)送 信號 )。 如果想要訪問被代理的對象,可以使用 _get_current_object() 方法:

app = current_app._get_current_object()
my_signal.send(app)

出錯時的環(huán)境保存

不管是否出錯,在請求結(jié)束時,請求環(huán)境會被彈出,并且所有相關(guān)聯(lián)的數(shù)據(jù)會被銷毀。 但是在開發(fā)過程中,可能需要在出現(xiàn)異常時保留相關(guān)信息。在 Flask 0.6 版本及更早的 版本中,在發(fā)生異常時,請求環(huán)境不會被彈出,以便于交互調(diào)試器提供重要信息。

自 Flask 0.7 版本開始,可以通過設(shè)置 PRESERVE_CONTEXT_ON_EXCEPTION 配置變量來更好地控制環(huán)境的保存。缺省情況下,這個配置變更與 DEBUG 變更關(guān)聯(lián)。如果在調(diào)試模式下,那么環(huán)境會被保留,而在生產(chǎn)模式下則不保留。

不要在生產(chǎn)環(huán)境下強(qiáng)制激活 PRESERVE_CONTEXT_ON_EXCEPTION ,因為這會在出現(xiàn)異常 時導(dǎo)致應(yīng)用內(nèi)存溢出。但是在調(diào)試模式下使用這個變更是十分有用的,你可以獲得在生產(chǎn)模式下出錯時的環(huán)境。

? Copyright 2013, Armin Ronacher. Created using Sphinx.

上一篇:排除應(yīng)用錯誤下一篇:信號