摘要:本文就主要針對(duì)一個(gè)應(yīng)用的運(yùn)行過程進(jìn)行簡(jiǎn)要分析,后續(xù)文章還會(huì)對(duì)框架的一些具體問題進(jìn)行分析。所有的請(qǐng)求處理過程,都會(huì)在這個(gè)上下文對(duì)象中進(jìn)行。和一些全局變量注意當(dāng)進(jìn)入這個(gè)上下文對(duì)象時(shí),會(huì)觸發(fā)。
相信很多初學(xué)Flask的同學(xué)(包括我自己),在閱讀官方文檔或者Flask的學(xué)習(xí)資料時(shí),對(duì)于它的認(rèn)識(shí)是從以下的一段代碼開始的:
from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Hello World!" if __name__ == "__main__": app.run()
運(yùn)行如上代碼,在瀏覽器中訪問http://localhost:5000/,便可以看到Hello World!出現(xiàn)了。這是一個(gè)很簡(jiǎn)單的Flask的應(yīng)用。
然而,這段代碼怎么運(yùn)行起來的呢?一個(gè)Flask應(yīng)用運(yùn)轉(zhuǎn)的背后又有哪些邏輯呢?如果你只關(guān)心Web應(yīng)用,那對(duì)這些問題不關(guān)注也可以,但從整個(gè)Web編程的角度來看,這些問題非常有意義。本文就主要針對(duì)一個(gè)Flask應(yīng)用的運(yùn)行過程進(jìn)行簡(jiǎn)要分析,后續(xù)文章還會(huì)對(duì)Flask框架的一些具體問題進(jìn)行分析。
為了分析方便,本文采用 Flask 0.1版本 的源碼進(jìn)行相關(guān)問題的探索。
一些準(zhǔn)備知識(shí)在正式分析Flask之前,有一些準(zhǔn)備知識(shí)需要先了解一下:
使用Flask框架開發(fā)的屬于Web應(yīng)用。由于Python使用WSGI網(wǎng)關(guān),所以這個(gè)應(yīng)用也可以叫WSGI應(yīng)用;
服務(wù)器、Web應(yīng)用的設(shè)計(jì)應(yīng)該遵循網(wǎng)關(guān)接口的一些規(guī)范。對(duì)于WSGI網(wǎng)關(guān),要求Web應(yīng)用實(shí)現(xiàn)一個(gè)函數(shù)或者一個(gè)可調(diào)用對(duì)象webapp(environ, start_response)。服務(wù)器或網(wǎng)關(guān)中要定義start_response函數(shù)并且調(diào)用Web應(yīng)用。關(guān)于這部分的內(nèi)容可以參考:wsgiref包——符合WSGI標(biāo)準(zhǔn)的Web服務(wù)實(shí)現(xiàn)(一)。
Flask依賴于底層庫(kù)werkzeug。相關(guān)內(nèi)容可以參考:Werkzeug庫(kù)簡(jiǎn)介。
本文暫時(shí)不對(duì)服務(wù)器或網(wǎng)關(guān)的具體內(nèi)容進(jìn)行介紹,只需對(duì)服務(wù)器、網(wǎng)關(guān)、Web應(yīng)用之間有怎樣的關(guān)系,以及它們之間如何調(diào)用有一個(gè)了解即可。
一個(gè)Flask應(yīng)用運(yùn)行的過程 1. 實(shí)例化一個(gè)Flask應(yīng)用使用app = Flask(__name__),可以實(shí)例化一個(gè)Flask應(yīng)用。實(shí)例化的Flask應(yīng)用有一些要點(diǎn)或特性需要注意一下:
對(duì)于請(qǐng)求和響應(yīng)的處理,F(xiàn)lask使用werkzeug庫(kù)中的Request類和Response類。對(duì)于這兩個(gè)類的相關(guān)內(nèi)容可以參考:Werkzeug庫(kù)——wrappers模塊。
對(duì)于URL模式的處理,F(xiàn)lask應(yīng)用使用werkzeug庫(kù)中的Map類和Rule類,每一個(gè)URL模式對(duì)應(yīng)一個(gè)Rule實(shí)例,這些Rule實(shí)例最終會(huì)作為參數(shù)傳遞給Map類構(gòu)造包含所有URL模式的一個(gè)“地圖”。這個(gè)地圖可以用來匹配請(qǐng)求中的URL信息,關(guān)于Map類和Rule類的相關(guān)知識(shí)可以參考:Werkzeug庫(kù)——routing模塊。
當(dāng)實(shí)例化一個(gè)Flask應(yīng)用app(這個(gè)應(yīng)用的名字可以隨便定義)之后,對(duì)于如何添加URL模式,F(xiàn)lask采取了一種更加優(yōu)雅的模式,對(duì)于這點(diǎn)可以和Django的做法進(jìn)行比較。Flask采取裝飾器的方法,將URL規(guī)則和視圖函數(shù)結(jié)合在一起寫,其中主要的函數(shù)是route。在上面例子中:
@app.route("/") def index(): pass
這樣寫視圖函數(shù),會(huì)將"/"這條URL規(guī)則和視圖函數(shù)index()聯(lián)系起來,并且會(huì)形成一個(gè)Rule實(shí)例,再添加進(jìn)Map實(shí)例中去。當(dāng)訪問"/"時(shí),會(huì)執(zhí)行index()。關(guān)于Flask匹配URL的內(nèi)容,可以參考后續(xù)文章。
實(shí)例化Flask應(yīng)用時(shí),會(huì)創(chuàng)造一個(gè)Jinja環(huán)境,這是Flask自帶的一種模板引擎。可以查看Jinja文檔,這里先暫時(shí)不做相關(guān)介紹。
實(shí)例化的Flask應(yīng)用是一個(gè)可調(diào)用對(duì)象。在前面講到,Web應(yīng)用要遵循WSGI規(guī)范,就要實(shí)現(xiàn)一個(gè)函數(shù)或者一個(gè)可調(diào)用對(duì)象webapp(environ, start_response),以方便服務(wù)器或網(wǎng)關(guān)調(diào)用。Flask應(yīng)用通過__call__(environ, start_response)方法可以讓它被服務(wù)器或網(wǎng)關(guān)調(diào)用。
def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`""" return self.wsgi_app(environ, start_response)
注意到調(diào)用該方法會(huì)執(zhí)行wsgi_app(environ, start_response)方法,之所以這樣設(shè)計(jì)是為了在應(yīng)用正式處理請(qǐng)求之前,可以加載一些“中間件”,以此改變Flask應(yīng)用的相關(guān)特性。對(duì)于這一點(diǎn)后續(xù)會(huì)詳細(xì)分析。
Flask應(yīng)用還有一些其他的屬性或方法,用于整個(gè)請(qǐng)求和響應(yīng)過程。
2.調(diào)用Flask應(yīng)用時(shí)會(huì)發(fā)生什么上面部分分析了實(shí)例化的Flask應(yīng)用長(zhǎng)什么樣子。當(dāng)一個(gè)完整的Flask應(yīng)用實(shí)例化后,可以通過調(diào)用app.run()方法運(yùn)行這個(gè)應(yīng)用。
Flask應(yīng)用的run()方法會(huì)調(diào)用werkzeug.serving模塊中的run_simple方法。這個(gè)方法會(huì)創(chuàng)建一個(gè)本地的測(cè)試服務(wù)器,并且在這個(gè)服務(wù)器中運(yùn)行Flask應(yīng)用。關(guān)于服務(wù)器的創(chuàng)建這里不做說明,可以查看werkzeug.serving模塊的有關(guān)文檔。
當(dāng)服務(wù)器開始調(diào)用Flask應(yīng)用后,便會(huì)觸發(fā)Flask應(yīng)用的__call__(environ, start_response)方法。其中environ由服務(wù)器產(chǎn)生,start_response在服務(wù)器中定義。
上面我們分析到當(dāng)Flask應(yīng)用被調(diào)用時(shí)會(huì)執(zhí)行wsgi_app(environ, start_response)方法。可以看出,wsgi_app是真正被調(diào)用的WSGI應(yīng)用,之所以這樣設(shè)計(jì),就是為了在應(yīng)用正式處理請(qǐng)求之前,wsgi_app可以被一些“中間件”裝飾,以便先行處理一些操作。為了便于理解,這里先舉兩個(gè)例子進(jìn)行說明。
例子一: 中間件SharedDataMiddleware中間件SharedDataMiddleware是werkzeug.wsgi模塊中的一個(gè)類。該類可以為Web應(yīng)用提供靜態(tài)內(nèi)容的支持。例如:
import os from werkzeug.wsgi import SharedDataMiddleware app = SharedDataMiddleware(app, { "/shared": os.path.join(os.path.dirname(__file__), "shared") })
Flask應(yīng)用通過以上的代碼,app便會(huì)成為一個(gè)SharedDataMiddleware實(shí)例,之后便可以在http://example.com/shared/中訪問shared文件夾下的內(nèi)容。
對(duì)于中間件SharedDataMiddleware,F(xiàn)lask應(yīng)用在初始實(shí)例化的時(shí)候便有所應(yīng)用。其中有這樣一段代碼:
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { self.static_path: target })
這段代碼顯然會(huì)將wsgi_app變成一個(gè)SharedDataMiddleware對(duì)象,這個(gè)對(duì)象為Flask應(yīng)用提供一個(gè)靜態(tài)文件夾/static。這樣,當(dāng)整個(gè)Flask應(yīng)用被調(diào)用時(shí),self.wsgi_app(environ, start_response)會(huì)執(zhí)行。由于此時(shí)self.wsgi_app是一個(gè)SharedDataMiddleware對(duì)象,所以會(huì)先觸發(fā)SharedDataMiddleware對(duì)象的__call__(environ, start_response)方法。如果此時(shí)的請(qǐng)示是要訪問/static這個(gè)文件夾,SharedDataMiddleware對(duì)象會(huì)直接返回響應(yīng);如果不是,則才會(huì)調(diào)用Flask應(yīng)用的wsgi_app(environ.start_response)方法繼續(xù)處理請(qǐng)求。
例子二: 中間件DispatcherMiddleware中間件DispatcherMiddleware也是werkzeug.wsgi模塊中的一個(gè)類。這個(gè)類可以講不同的應(yīng)用“合并”起來。以下是一個(gè)使用中間件DispatcherMiddleware的例子。
from flask import Flask from werkzeug import DispatcherMiddleware app1 = Flask(__name__) app2 = Flask(__name__) app = Flask(__name__) @app1.route("/") def index(): return "This is app1!" @app2.route("/") def index(): return "This is app2!" @app.route("/") def index(): return "This is app!" app = DispatcherMiddleware(app, { "/app1": app1, "/app2": app2 }) if __name__ == "__main__": from werkzeug.serving import run_simple run_simple("localhost", 5000, app)
在上面的例子中,我們首先創(chuàng)建了三個(gè)不同的Flask應(yīng)用,并為每個(gè)應(yīng)用創(chuàng)建了一個(gè)視圖函數(shù)。但是,我們使用了DispatcherMiddleware,將app1、app2和app合并起來。這樣,此時(shí)的app便成為一個(gè)DispatcherMiddleware對(duì)象。
當(dāng)在服務(wù)器中調(diào)用app時(shí),由于它是一個(gè)DispatcherMiddleware對(duì)象,所以首先會(huì)觸發(fā)它的__call__(environ, start_response)方法。然后根據(jù)請(qǐng)求URL中的信息來確定要調(diào)用哪個(gè)應(yīng)用。例如:
如果訪問/,則會(huì)觸發(fā)app(environ, start_response)(注意: 此時(shí)app是一個(gè)Flask對(duì)象),進(jìn)而處理要訪問app的請(qǐng)求;
如果訪問/app1,則會(huì)觸發(fā)app1(environ, start_response),進(jìn)而處理要訪問app1的請(qǐng)求。訪問/app2同理。
3. 和請(qǐng)求處理相關(guān)的上下文對(duì)象當(dāng)Flask應(yīng)用真正處理請(qǐng)求時(shí),wsgi_app(environ, start_response)被調(diào)用。這個(gè)函數(shù)是按照下面的方式運(yùn)行的:
def wsgi_app(environ, start_response): with self.request_context(environ): ...請(qǐng)求上下文
可以看到,當(dāng)Flask應(yīng)用處理一個(gè)請(qǐng)求時(shí),會(huì)構(gòu)造一個(gè)上下文對(duì)象。所有的請(qǐng)求處理過程,都會(huì)在這個(gè)上下文對(duì)象中進(jìn)行。這個(gè)上下文對(duì)象是_RequestContext類的實(shí)例。
# Flask v0.1 class _RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided. """ def __init__(self, app, environ): self.app = app self.url_adapter = app.url_map.bind_to_environ(environ) self.request = app.request_class(environ) self.session = app.open_session(self.request) self.g = _RequestGlobals() self.flashes = None def __enter__(self): _request_ctx_stack.push(self) def __exit__(self, exc_type, exc_value, tb): # do not pop the request stack if we are in debug mode and an # exception happened. This will allow the debugger to still # access the request object in the interactive shell. if tb is None or not self.app.debug: _request_ctx_stack.pop()
根據(jù)_RequestContext上下文對(duì)象的定義,可以發(fā)現(xiàn),在構(gòu)造這個(gè)對(duì)象的時(shí)候添加了和Flask應(yīng)用相關(guān)的一些屬性:
app ——上下文對(duì)象的app屬性是當(dāng)前的Flask應(yīng)用;
url_adapter ——上下文對(duì)象的url_adapter屬性是通過Flask應(yīng)用中的Map實(shí)例構(gòu)造成一個(gè)MapAdapter實(shí)例,主要功能是將請(qǐng)求中的URL和Map實(shí)例中的URL規(guī)則進(jìn)行匹配;
request ——上下文對(duì)象的request屬性是通過Request類構(gòu)造的實(shí)例,反映請(qǐng)求的信息;
session ——上下文對(duì)象的session屬性存儲(chǔ)請(qǐng)求的會(huì)話信息;
g ——上下文對(duì)象的g屬性可以存儲(chǔ)全局的一些變量。
flashes ——消息閃現(xiàn)的信息。
LocalStack和一些“全局變量”注意: 當(dāng)進(jìn)入這個(gè)上下文對(duì)象時(shí),會(huì)觸發(fā)_request_ctx_stack.push(self)。在這里需要注意Flask中使用了werkzeug庫(kù)中定義的一種數(shù)據(jù)結(jié)構(gòu)LocalStack。
_request_ctx_stack = LocalStack()
關(guān)于LocalStack,可以參考:Werkzeug庫(kù)——local模塊。LocalStack是一種棧結(jié)構(gòu),每當(dāng)處理一個(gè)請(qǐng)求時(shí),請(qǐng)求上下文對(duì)象_RequestContext會(huì)被放入這個(gè)棧結(jié)構(gòu)中。數(shù)據(jù)在棧中存儲(chǔ)的形式表現(xiàn)成如下:
{880: {"stack": []}, 13232: {"stack": [ ]}}
這是一個(gè)字典形式的結(jié)構(gòu),鍵代表當(dāng)前線程/協(xié)程的標(biāo)識(shí)數(shù)值,值代表當(dāng)前線程/協(xié)程存儲(chǔ)的變量。werkzeug.local模塊構(gòu)造的這種結(jié)構(gòu),很容易實(shí)現(xiàn)線程/協(xié)程的分離。也正是這種特性,使得可以在Flask中訪問以下的“全局變量”:
current_app = LocalProxy(lambda: _request_ctx_stack.top.app) request = LocalProxy(lambda: _request_ctx_stack.top.request) session = LocalProxy(lambda: _request_ctx_stack.top.session) g = LocalProxy(lambda: _request_ctx_stack.top.g)
其中_request_ctx_stack.top始終指向當(dāng)前線程/協(xié)程中存儲(chǔ)的“請(qǐng)求上下文”,這樣像app、request、session、g等都可以以“全局”的形式存在。這里“全局”是指在當(dāng)前線程或協(xié)程當(dāng)中。
由此可以看出,當(dāng)處理請(qǐng)求時(shí):
首先,會(huì)生成一個(gè)請(qǐng)求上下文對(duì)象,這個(gè)上下文對(duì)象包含請(qǐng)求相關(guān)的信息。并且在進(jìn)入上下文環(huán)境時(shí),LocalStack會(huì)將這個(gè)上下文對(duì)象推入棧結(jié)構(gòu)中以存儲(chǔ)這個(gè)對(duì)象;
在這個(gè)上下文環(huán)境中可以進(jìn)行請(qǐng)求處理過程,這個(gè)稍后再介紹。不過可以以一種“全局”的方式訪問上下文對(duì)象中的變量,例如app、request、session、g等;
當(dāng)請(qǐng)求結(jié)束,退出上下文環(huán)境時(shí),LocalStack會(huì)清理當(dāng)前線程/協(xié)程產(chǎn)生的數(shù)據(jù)(請(qǐng)求上下文對(duì)象);
Flask 0.1版本只有“請(qǐng)求上下文”的概念,在Flask 0.9版本中又增加了“應(yīng)用上下文”的概念。關(guān)于“應(yīng)用上下文”,以后再加以分析。
4. 在上下文環(huán)境中處理請(qǐng)求處理請(qǐng)求的過程定義在wsgi_app方法中,具體如下:
def wsgi_app(environ, start_response): with self.request_context(environ): rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() response = self.make_response(rv) response = self.process_response(response) return response(environ, start_response)
從代碼可以看出,在上下文對(duì)象中處理請(qǐng)求的過程分為以下幾個(gè)步驟:
在請(qǐng)求正式被處理之前的一些操作,調(diào)用preprocess_request()方法,例如打開一個(gè)數(shù)據(jù)庫(kù)連接等操作;
正式處理請(qǐng)求。這個(gè)過程調(diào)用dispatch_request()方法,這個(gè)方法會(huì)根據(jù)URL匹配的情況調(diào)用相關(guān)的視圖函數(shù);
將從視圖函數(shù)返回的值轉(zhuǎn)變?yōu)橐粋€(gè)Response對(duì)象;
在響應(yīng)被發(fā)送到WSGI服務(wù)器之前,調(diào)用process_response(response)做一些后續(xù)處理過程;
調(diào)用response(environ, start_response)方法將響應(yīng)發(fā)送回WSGI服務(wù)器。關(guān)于此方法的使用,可以參考:Werkzeug庫(kù)——wrappers模塊;
退出上下文環(huán)境時(shí),LocalStack會(huì)清理當(dāng)前線程/協(xié)程產(chǎn)生的數(shù)據(jù)(請(qǐng)求上下文對(duì)象)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/38605.html
摘要:并且棧頂?shù)脑囟际堑恼?qǐng)求上下文和應(yīng)用上下文之后,我們?cè)僭谶@個(gè)環(huán)境中嵌套的應(yīng)用上下文。這時(shí)查看兩個(gè)棧的內(nèi)容,發(fā)現(xiàn)兩個(gè)棧中只有的請(qǐng)求的請(qǐng)求上下文對(duì)象和應(yīng)用上下文對(duì)象。而等一直指向棧頂?shù)恼?qǐng)求上下文對(duì)象,分別引用請(qǐng)求上下文的和。 在Flask中處理請(qǐng)求時(shí),應(yīng)用會(huì)生成一個(gè)請(qǐng)求上下文對(duì)象。整個(gè)請(qǐng)求的處理過程,都會(huì)在這個(gè)上下文對(duì)象中進(jìn)行。這保證了請(qǐng)求的處理過程不被干擾。處理請(qǐng)求的具體代碼如下: de...
摘要:蠎周刊年度最贊親俺們又來回顧又一個(gè)偉大的年份兒包去年最受歡迎的文章和項(xiàng)目如果你錯(cuò)過了幾期就這一期不會(huì)丟失最好的嗯哼還為你和你的準(zhǔn)備了一批紀(jì)念裇從這兒獲取任何時(shí)候如果想分享好物給大家在這兒提交喜歡我們收集的任何意見建議通過來吧原文 Title: 蠎周刊 2015 年度最贊Date: 2016-01-09 Tags: Weekly,Pycoder,Zh Slug: issue-198-to...
摘要:前言去年十月開始學(xué)習(xí)一開始寫了一個(gè)的爬蟲將自己在過程中的一些經(jīng)驗(yàn)寫了下來沒想到那么多人支持。但目前也只是處于能用狀態(tài)。及如何將一個(gè)文件夾下文件變成一個(gè)包呢。而不僅僅是一個(gè)服務(wù)器無法理解此請(qǐng)求。 前言 去年十月開始學(xué)習(xí)python一開始寫了一個(gè)python的爬蟲 將自己在過程中的一些經(jīng)驗(yàn)寫了下來沒想到那么多人支 持。之后因?yàn)橐恍?shí)驗(yàn)室的需求就轉(zhuǎn)投python的web開發(fā) 一開...
摘要:我們將創(chuàng)建一個(gè)簡(jiǎn)單的,它將從到返回一個(gè)隨機(jī)數(shù)。我們來改變組件顯示隨機(jī)數(shù)在這個(gè)階段,我們只是模仿客戶端的隨機(jī)數(shù)生成過程。 在這個(gè)教程中,我們將講解如何將vue.js單頁(yè)應(yīng)用與Flask后端進(jìn)行連接。 一般來說,如果你只是想通過Flask模板使用vue.js庫(kù)也是沒有問題的。但是,實(shí)際上是一個(gè)很明顯的問題那就是,Jinja(模板引擎)也和Vue.js一樣采用雙大括號(hào)用于渲染,但只是一個(gè)還算...
摘要:更改執(zhí)行策略可能會(huì)產(chǎn)生安全風(fēng)險(xiǎn),如中的幫助主題所述。如果出現(xiàn)選擇環(huán)境,我們選擇。在中,我們僅保留這一段。在中,我們新建一個(gè)文件,名為。到此,我們的環(huán)境配置就完成了。 在 Visual Studio Code 中配置 Python Flask 環(huán)境 本文由 赤石俊哉 原創(chuàng)編寫,您可以在學(xué)習(xí)交流用途以內(nèi)自由使用文章。 但是禁止抄襲文章,轉(zhuǎn)載時(shí),請(qǐng)注明來源地址,謝謝。最后更新時(shí)間: 20...
閱讀 4568·2021-09-22 14:57
閱讀 561·2019-08-30 15:56
閱讀 2663·2019-08-30 15:53
閱讀 2239·2019-08-29 14:15
閱讀 1686·2019-08-28 17:54
閱讀 558·2019-08-26 13:37
閱讀 3476·2019-08-26 10:57
閱讀 1044·2019-08-26 10:32