請求上下文
在flask 0.9版本之前,flask中只有“請求上下文”的概念。那什么是請求上下文呢棵里?
我們先回憶一下在寫flask程序的時候姐呐,經(jīng)常會碰到直接調(diào)用像current_app、request典蝌、session曙砂、g等變量。這些變量看起來似乎是全局變量骏掀,但是實質(zhì)上這些變量并不是真正意義上的全局變量鸠澈。如果將這些變量設(shè)置為全局變量,試想一下截驮,多個線程同時請求程序訪問這些變量時勢必會相互影響笑陈。但是如果不設(shè)置為全局變量,那在編寫代碼時每次都要顯式地傳遞這些變量也是一件非常令人頭疼的事情葵袭,而且還容易出錯涵妥。
為了解決這些問題,flask設(shè)計時采取線程隔離的思路坡锡,也就是說在一次請求的一個線程中可以將其設(shè)置為全局變量蓬网,但是僅限于請求的這個線程內(nèi)部,不同線程通過“線程標(biāo)識符”來區(qū)別鹉勒。這樣就不會影響到其他線程的請求帆锋。flask實現(xiàn)線程隔離主要是使用werkzeug中的兩個類:Local和LocalProxy,這里不再贅述禽额,可以去看看werkzeug的源碼了解一下實現(xiàn)過程锯厢。
實現(xiàn)線程隔離后,為了在一個線程中更加方便使用這些變量绵疲,flask中還有一種堆棧的數(shù)據(jù)結(jié)構(gòu)(通過werkzeug的LocalStack實現(xiàn))哲鸳,可以處理這些變量,但是并不直接處理這些變量盔憨。假如有一個程序得到一個請求徙菠,那么flask會將這個請求的所有相關(guān)信息進(jìn)行打包,打包形成的東西就是處理請求的一個環(huán)境郁岩。flask將這種環(huán)境稱為“請求上下文”(request context)婿奔,之后flask會將這個請求上下文對象放到堆棧中。
這樣问慎,請求發(fā)生時萍摊,我們一般都會指向堆棧中的“請求上下文”對象,這樣可以通過請求上下文獲取相關(guān)對象并直接訪問如叼,例如current_app冰木、request、session、g踊沸。還可以通過調(diào)用對象的方法或者屬性獲取其他信息歇终,例如request.method。等請求結(jié)束后逼龟,請求上下文會被銷毀评凝,堆棧重新等待新的請求上下文對象被放入。
應(yīng)用上下文
應(yīng)用上下文的概念是在flask 0.9中增加的腺律。
既然flask通過線程隔離的方式奕短,將一些變量設(shè)置為線程內(nèi)的“全局”可用。由于請求上下文中包含有當(dāng)前應(yīng)用相關(guān)的信息匀钧,那也就是說可以通過調(diào)用current_app就可以獲取請求所在的正確應(yīng)用而不會導(dǎo)致混淆翎碑。那為什么需要增加一個應(yīng)用上下文的概念呢?
對于單應(yīng)用單請求來說榴捡,使用“請求上下文”確實就可以了杈女。然而,F(xiàn)lask的設(shè)計理念之一就是多應(yīng)用的支持。當(dāng)在一個應(yīng)用的請求上下文環(huán)境中,需要嵌套處理另一個應(yīng)用的相關(guān)操作時(這種情況更多的是用于測試或者在console中對多個應(yīng)用進(jìn)行相關(guān)處理)姆打,“請求上下文”顯然就不能很好地解決問題了根吁,因為魔法current_app無法確定當(dāng)前處理的到底是哪個應(yīng)用。如何讓請求找到“正確”的應(yīng)用呢?我們可能會想到,可以再增加一個請求上下文環(huán)境,并將其推入棧中蝇裤。由于兩個上下文環(huán)境的運行是獨立的,不會相互干擾频鉴,所以通過調(diào)用棧頂對象的app屬性或者調(diào)用current_app(current_app一直指向棧頂?shù)膶ο?也可以獲得當(dāng)前上下文環(huán)境正在處理哪個應(yīng)用栓辜。這種辦法在一定程度上可行,但是如果說對第二個應(yīng)用的處理不涉及到相關(guān)請求垛孔,那也就無從談起“請求上下文”藕甩,更不可能建立請求上下文環(huán)境了。
為了應(yīng)對這個問題周荐,F(xiàn)lask中將應(yīng)用相關(guān)的信息單獨拿出來狭莱,形成一個“應(yīng)用上下文”對象。這個對象可以和“請求上下文”一起使用概作,也可以單獨拿出來使用腋妙。不過有一點需要注意的是:在創(chuàng)建“請求上下文”時一定要創(chuàng)建一個“應(yīng)用上下文”對象。有了“應(yīng)用上下文”對象讯榕,便可以很容易地確定當(dāng)前處理哪個應(yīng)用骤素,這就是魔法`current_app`匙睹。在0.1版本中,current_app是對_request_ctx_stack.top.app的引用谆甜,而在0.9版本中current_app是對_app_ctx_stack.top.app的引用垃僚。其中_request_ctx_stack和_app_ctx_stack分別是存儲請求上下文和應(yīng)用上下文的棧。
這里舉一個多應(yīng)用的例子:
假設(shè)有兩個Flask應(yīng)用:app1和app2规辱。我們假定一種情形:在請求訪問app1時,先要對app2進(jìn)行一些操作栽燕,之后再處理app1內(nèi)的請求罕袋。以下是一些分析過程:
請求訪問app1時,app1會生成一個請求上下文對象碍岔,并且使用with語句產(chǎn)生一個請求上下文環(huán)境浴讯。請求處理的所有過程都會在這個上下文環(huán)境中進(jìn)行。當(dāng)進(jìn)入這個上下文環(huán)境時蔼啦,F(xiàn)lask會將請求上下文對象推入_request_ctx_stack這個棧中榆纽,并且生成一個對應(yīng)的應(yīng)用上下文對象,將其推入_app_ctx_stack棧中捏肢。_app_ctx_stack棧頂指向的是app1奈籽;
當(dāng)在app1的請求上下文環(huán)境中需要對app2進(jìn)行操作時,為了和app1的相關(guān)操作隔離開來鸵赫,可以使用with語句建立一個app2的應(yīng)用上下文環(huán)境衣屏。在這個過程中,會新建一個應(yīng)用上下文對象辩棒,并將其推入_app_ctx_stack棧中狼忱。_app_ctx_stack棧頂指向的是app2;
當(dāng)退出app2的應(yīng)用上下文環(huán)境一睁,重新進(jìn)入app1的請求上下文環(huán)境時钻弄,_app_ctx_stack棧會銷毀app2的應(yīng)用上下文對象,它的棧頂指向的是app1者吁。
通過以上一個假象的例子窘俺,我們始終可以使用current_app來表示當(dāng)前處理的Flask應(yīng)用。