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