一個Flask應用執行過程剖析
相信很多初學Flask的同學(包括我自己),在閱讀官方文件或者Flask的學習資料時,對於它的認識是從以下的一段程式碼開始的:
12345678910 | from |
執行如上程式碼,在瀏覽器中訪問http://localhost:5000/
,便可以看到Hello World!
出現了。這是一個很簡單的Flask的應用。
然而,這段程式碼怎麼執行起來的呢?一個Flask應用運轉的背後又有哪些邏輯呢?如果你只關心Web應用,那對這些問題不關注也可以,但從整個Web程式設計的角度來看,這些問題非常有意義。本文就主要針對一個Flask應用的執行過程進行簡要分析,後續文章還會對Flask框架的一些具體問題進行分析。
為了分析方便,本文采用 Flask 0.1版本 的原始碼進行相關問題的探索。
一些準備知識
在正式分析Flask之前,有一些準備知識需要先了解一下:
- 使用Flask框架開發的屬於Web應用。由於Python使用
WSGI
閘道器,所以這個應用也可以叫WSGI
應用; - 伺服器、Web應用的設計應該遵循閘道器介面的一些規範。對於
WSGI
閘道器,要求Web應用實現一個函式或者一個可呼叫物件webapp(environ, start_response)
。伺服器或閘道器中要定義start_response
函式並且呼叫Web應用。關於這部分的內容可以參考:wsgiref包——符合WSGI標準的Web服務實現(一) - Flask依賴於底層庫
werkzeug
。相關內容可以參考:Werkzeug庫簡介。
本文暫時不對伺服器或閘道器的具體內容進行介紹,只需對伺服器、閘道器、Web應用之間有怎樣的關係,以及它們之間如何呼叫有一個瞭解即可。
一個Flask應用執行的過程
1. 例項化一個Flask應用
使用app = Flask(__name__)
,可以例項化一個Flask應用。例項化的Flask應用有一些要點或特性需要注意一下:
- 對於請求和響應的處理,Flask使用
werkzeug
庫中的Request
類和Response
類。對於這兩個類的相關內容可以參考:Werkzeug庫——wrappers模組。 - 對於URL模式的處理,Flask應用使用
werkzeug
庫中的Map
類和Rule
類,每一個URL模式對應一個Rule
例項,這些Rule
例項最終會作為引數傳遞給Map
類構造包含所有URL模式的一個“地圖”。這個地圖可以用來匹配請求中的URL資訊,關於Map
類和Rule
類的相關知識可以參考:Werkzeug庫——routing模組。 - 當例項化一個Flask應用
app
(這個應用的名字可以隨便定義)之後,對於如何新增URL模式,Flask採取了一種更加優雅的模式,對於這點可以和Django的做法進行比較。Flask採取裝飾器的方法,將URL規則和檢視函式結合在一起寫,其中主要的函式是route
。在上面例子中:
123 @app.route('/')def index():pass
這樣寫檢視函式,會將'/'
這條URL規則和檢視函式index()
聯絡起來,並且會形成一個Rule
例項,再新增進Map
例項中去。當訪問'/'
時,會執行index()
。關於Flask匹配URL的內容,可以參考後續文章。 - 例項化Flask應用時,會創造一個
Jinja
環境,這是Flask自帶的一種模板引擎。可以檢視Jinja文件,這裡先暫時不做相關介紹。 - 例項化的Flask應用是一個可呼叫物件。在前面講到,Web應用要遵循
WSGI
規範,就要實現一個函式或者一個可呼叫物件webapp(environ, start_response)
,以方便伺服器或閘道器呼叫。Flask應用通過__call__(environ, start_response)
方法可以讓它被伺服器或閘道器呼叫。
123 def __call__(self,environ,start_response):"""Shortcut for :attr:`wsgi_app`"""returnself.wsgi_app(environ,start_response)
注意到呼叫該方法會執行wsgi_app(environ, start_response)
方法,之所以這樣設計是為了在應用正式處理請求之前,可以載入一些“中介軟體”,以此改變Flask應用的相關特性。對於這一點後續會詳細分析。 - Flask應用還有一些其他的屬性或方法,用於整個請求和響應過程。
2.呼叫Flask應用時會發生什麼
上面部分分析了例項化的Flask應用長什麼樣子。當一個完整的Flask應用例項化後,可以通過呼叫app.run()
方法執行這個應用。
Flask應用的run()
方法會呼叫werkzeug.serving
模組中的run_simple
方法。這個方法會建立一個本地的測試伺服器,並且在這個伺服器中執行Flask應用。關於伺服器的建立這裡不做說明,可以檢視werkzeug.serving
模組的有關文件。
當伺服器開始呼叫Flask應用後,便會觸發Flask應用的__call__(environ, start_response)
方法。其中environ
由伺服器產生,start_response
在伺服器中定義。
上面我們分析到當Flask應用被呼叫時會執行wsgi_app(environ, start_response)
方法。可以看出,wsgi_app
是真正被呼叫的WSGI
應用,之所以這樣設計,就是為了在應用正式處理請求之前,wsgi_app
可以被一些“中介軟體”裝飾,以便先行處理一些操作。為了便於理解,這裡先舉兩個例子進行說明。
例子一: 中介軟體SharedDataMiddleware
中介軟體SharedDataMiddleware
是werkzeug.wsgi
模組中的一個類。該類可以為Web應用提供靜態內容的支援。例如:
123456 | import osfrom werkzeug.wsgi import SharedDataMiddlewareapp=SharedDataMiddleware(app,{'/shared':os.path.join(os.path.dirname(__file__),'shared')}) |
Flask應用通過以上的程式碼,app
便會成為一個SharedDataMiddleware
例項,之後便可以在http://example.com/shared/
中訪問shared
資料夾下的內容。
對於中介軟體SharedDataMiddleware,Flask應用在初始例項化的時候便有所應用。其中有這樣一段程式碼:
123 | self.wsgi_app=SharedDataMiddleware(self.wsgi_app,{self.static_path:target}) |
這段程式碼顯然會將wsgi_app
變成一個SharedDataMiddleware
物件,這個物件為Flask應用提供一個靜態資料夾/static
。這樣,當整個Flask應用被呼叫時,self.wsgi_app(environ, start_response)
會執行。由於此時self.wsgi_app
是一個SharedDataMiddleware
物件,所以會先觸發SharedDataMiddleware
物件的__call__(environ, start_response)
方法。如果此時的請示是要訪問/static
這個資料夾,SharedDataMiddleware
物件會直接返回響應;如果不是,則才會呼叫Flask應用的wsgi_app(environ.start_response)
方法繼續處理請求。
例子二: 中介軟體DispatcherMiddleware
中介軟體DispatcherMiddleware
也是werkzeug.wsgi
模組中的一個類。這個類可以講不同的應用“合併”起來。以下是一個使用中介軟體DispatcherMiddleware
的例子。
123456789101112131415161718192021222324252627 | from flask import Flaskfrom werkzeug import DispatcherMiddlewareapp1=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_simplerun_simple('localhost',5000,app) |
在上面的例子中,我們首先建立了三個不同的Flask應用,併為每個應用建立了一個檢視函式。但是,我們使用了DispatcherMiddleware
,將app1
、app2
和app
合併起來。這樣,此時的app
便成為一個DispatcherMiddleware
物件。
當在伺服器中呼叫app
時,由於它是一個DispatcherMiddleware
物件,所以首先會觸發它的__call__(environ, start_response)
方法。然後根據請求URL中的資訊來確定要呼叫哪個應用。例如:
- 如果訪問
/
,則會觸發app(environ, start_response)
(注意: 此時app是一個Flask物件),進而處理要訪問app
的請求; - 如果訪問
/app1
,則會觸發app1(environ, start_response)
,進而處理要訪問app1
的請求。訪問/app2
同理。
3. 和請求處理相關的上下文物件
當Flask應用真正處理請求時,wsgi_app(environ, start_response)
被呼叫。這個函式是按照下面的方式執行的:
123 | def wsgi_app(environ,start_response):with self.request_context(environ):... |
請求上下文
可以看到,當Flask應用處理一個請求時,會構造一個上下文物件。所有的請求處理過程,都會在這個上下文物件中進行。這個上下文物件是_RequestContext
類的例項。
12345678910111213141516171819202122232425 | # Flask v0.1class_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=appself.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=Nonedef __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.iftb isNone ornotself.app.debug:_request_ctx_stack.pop() |
根據_RequestContext
上下文物件的定義,可以發現,在構造這個物件的時候添加了和Flask應用相關的一些屬性:
app
——上下文物件的app
屬性是當前的Flask應用;url_adapter
——上下文物件的url_adapter
屬性是通過Flask應用中的Map
例項構造成一個MapAdapter
例項,主要功能是將請求中的URL和Map
例項中的URL規則進行匹配;request
——上下文物件的request
屬性是通過Request
類構造的例項,反映請求的資訊;session
——上下文物件的session
屬性儲存請求的會話資訊;g
——上下文物件的g
屬性可以儲存全域性的一些變數。flashes
——訊息閃現的資訊。
LocalStack和一些“全域性變數”
注意: 當進入這個上下文物件時,會觸發_request_ctx_stack.push(self)
。在這裡需要注意Flask中使用了werkzeug
庫中定義的一種資料結構LocalStack
。
1 | _request_ctx_stack=LocalStack() |
關於LocalStack
,可以參考:Werkzeug庫——local模組。LocalStack
是一種棧結構,每當處理一個請求時,請求上下文物件_RequestContext
會被放入這個棧結構中。資料在棧中儲存的形式表現成如下:
1 | {880:{'stack':[<flask._RequestContext object>]},13232:{'stack':[<flask._RequestContext object>]}} |
這是一個字典形式的結構,鍵代表當前執行緒/協程的標識數值,值代表當前執行緒/協程儲存的變數。werkzeug.local
模組構造的這種結構,很容易實現執行緒/協程的分離。也正是這種特性,使得可以在Flask中訪問以下的“全域性變數”:
1234 | 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
始終指向當前執行緒/協程中儲存的“請求上下文”,這樣像app
、request
、session
、g
等都可以以“全域性”的形式存在。這裡“全域性”是指在當前執行緒或協程當中。
由此可以看出,當處理請求時:
- 首先,會生成一個請求上下文物件,這個上下文物件包含請求相關的資訊。並且在進入上下文環境時,
LocalStack
會將這個上下文物件推入棧結構中以儲存這個物件; - 在這個上下文環境中可以進行請求處理過程,這個稍後再介紹。不過可以以一種“全域性”的方式訪問上下文物件中的變數,例如
app
、request
、session
、g
等; - 當請求結束,退出上下文環境時,
LocalStack
會清理當前執行緒/協程產生的資料(請求上下文物件); - Flask 0.1版本只有“請求上下文”的概念,在Flask 0.9版本中又增加了“應用上下文”的概念。關於“應用上下文”,以後再加以分析。
4. 在上下文環境中處理請求
處理請求的過程定義在wsgi_app
方法中,具體如下:
12345678 | def wsgi_app(environ,start_response):with self.request_context(environ):rv=self.preprocess_request()ifrv isNone:rv=self.dispatch_request()response=self.make_response(rv)response=self.process_response(response)returnresponse(environ,start_response) |
從程式碼可以看出,在上下文物件中處理請求的過程分為以下幾個步驟:
- 在請求正式被處理之前的一些操作,呼叫
preprocess_request()
方法,例如開啟一個數據庫連線等操作; - 正式處理請求。這個過程呼叫
dispatch_request()
方法,這個方法會根據URL匹配的情況呼叫相關的檢視函式; - 將從檢視函式返回的值轉變為一個
Response
物件; - 在響應被髮送到
WSGI
伺服器之前,呼叫process_response(response)
做一些後續處理過程; - 呼叫
response(environ, start_response)
方法將響應傳送回WSGI
伺服器。關於此方法的使用,可以參考:Werkzeug庫——wrappers模組; - 退出上下文環境時,
LocalStack
會清理當前執行緒/協程產生的資料(請求上下文物件)。
相關推薦
一個Flask應用執行過程剖析
相信很多初學Flask的同學(包括我自己),在閱讀官方文件或者Flask的學習資料時,對於它的認識是從以下的一段程式碼開始的: from flask import Flask app = Flask(__name__) @app.route('/
第一個flask應用代碼詳解
python flask 上一篇我們創建了第一個簡單的flask應用程序,這一篇我們來看一下,這個最簡單的應用程序都做了哪些事 第一行代碼,導入了flask類 from flask import Flask 第二步創建了Flask類的實例 app = Flask(__name__) 這行代碼裏
第8章2節《MonkeyRunner源代碼剖析》MonkeyRunner啟動執行過程-解析處理命令行參數
path 轉載 iss 命令 code rst pri bsp ack MonkeyRunnerStarter是MonkeyRunner啟動時的入口類,由於它裏面包括了main方法.它的整個啟動過程主要做了以下幾件事情:解析用戶啟動MonkeyRunner時從命令行傳輸
一個簡單的flask應用
啟動服務 show 裝飾 hello 調試器 href deb ref 動態變量 一個簡單的flask應用,文件名hello.py from flask import Flask app = Flask(__name__) @app.route(‘/‘) def
Yii2應用的執行過程
每一個框架都有一個入口指令碼,Yii2也不例外。一般來說,對於Web應用的入口指令碼是YiiBasePath/frontend/web目錄下的index.php。 先觀察這個檔案: <?php defined('YII_DEBUG') or define('YII_DEBUG
Flask中session源碼執行過程
behavior 使用 dfs display all save -- img request 1.面向對象補充知識metaclass 創建類就可以有兩種方式: a.普通方式 1 class Foo(object): 2 3 def func(self):
一個簡單的CMake工程示例以及執行過程
在工程目錄下,構建目錄src,include,lib,bin。在src目錄下存放原始碼檔案,include目錄下存放標頭檔案,lib目錄用於存放生成的庫(動態庫或者靜態庫),bin目錄存放最終生成的可執行檔案。 src目錄存放main.cpp和lib_demo.cpp的原始
實現一個最簡單的flask應用程式
首先先將flask包匯入,建立flask物件,然後使用flask路由器指定網址和控制器。最後使用程式入口將flask應用啟動,port引數用來調整埠號,flask模組預設埠號為5000 程式碼如下: from flask import Flask app = Flask(_
weblogic伺服器下發應用的過程與後期weblogic獨立伺服器的執行描述。
當我們的weblogic伺服器安裝完畢後就是weblogic伺服器部署了。這裡將詳細介紹weblogic的部署以及部署完畢後,已部署的受管伺服器的獨立執行。 第一步部署應用,部署應用分兩種管理伺服器部署 生產模式下的管理伺服器,與開發模式下的管理伺服器。 開
IntelliJ IDEA 執行你的第一個Java應用程式 idea執行main方法
IntelliJ IDEA 執行你的第一個Java應用程式 建立專案讓我們建立一個簡單的Java Hello World專案。 單擊建立新的專案。 開啟新建專案嚮導。 你應該注意的主要是專案的SDK。SDK(軟體開發套件)是一套軟體開發工具,可以讓你更快的開發應用程式。IntelliJ IDEA
使用app dispatch技術將多個Flask應用組合成一個
動機 最近我們機器人後臺系統承接的業務越來越多,除了機器人本身的後臺,還有門禁系統、廣告系統等的後臺,都需要單獨的登入流程(門禁還細分PC端小區物業登入和app端業主登入),但是我發現flask-login跟Flask app是一一對應關係,即一個app內只可
用_Docker、Gradle_來構建、執行、釋出一個_Spring_Boot_應用
本文演示瞭如何用 Docker、Gradle 來構建、執行、釋出來一個 Spring Boot 應用。 Docker 簡介 Docker是一個 Linux 容器管理工具包,具備“社交”方面,允許使用者釋出容器的 image (映象),並使用別人釋出的 image。Docke
Tomcat配置多個Service,多個同名的應用執行在一個tomcat下
網站專案中有時候會碰到,每個網站一個網站後臺程式,偏偏因為某種原因,這後臺程式的名稱還是一樣的,比如都叫 app.war,這樣如果有2個或以上網站需要用各自的app.war,那麼就需要安裝多個tomcat,資源浪費比較大。也不好管理。有沒有一種可能,將這幾個app.war放到
Flask學習筆記:建立一個簡單的Flask應用
1. 做好準備工作進入專案主目錄啟用虛擬環境2. 建立app包:在flask中,含有名為 __init__.py 檔案的子目錄被視為包,可以被匯入。在命令列輸入以下命令,建立一個名為app的目錄:(venv) $ mkdir app在app目錄中建立一個名為__init__.
【PHP7核心剖析】3.3 Zend引擎執行過程
3.3 Zend引擎執行過程 Zend引擎主要包含兩個核心部分:編譯、執行: 前面分析了Zend的編譯過程以及PHP使用者函式的實現,接下來分析下Zend引擎的執行過程。 3.3.1 資料結構 執行流程中有幾個重要的資料結構,先看下這幾個
pthread_create執行緒建立的過程剖析
概述 在Linux環境下,pthread庫提供的pthread_create()API函式,用於建立一個執行緒。執行緒建立失敗時,它可能會返回ENOMEM或EAGAIN。這篇文章主要討論執行緒建立過程中碰到的一些問題和解決方法。 建立執行緒 首先,本文用的例項程式碼exam
一個win32應用程式檔案的啟動過程
學習windows 程式設計從mfc角度來說可分為兩部分那就是WinMain函式以前的,和WinMain函式以後的。前者涉及很多windows作業系統內部的知識,後者需要看mfc原始碼。雖然大多數程式不需要了解太多關於os載入應用程式這方面的知識,但能較深入瞭解windows os的執行情況對程式設計師是很有
Docker執行第一個Java應用
上篇部落格我們介紹瞭如何安裝Docker以及執行HelloWorld,這篇我們介紹一下如何執行第一個Java應用。在這裡我們用網上的JPress來做測試。首先我們去終端拉取tomcat映象將JPress.war放到指定目錄建立並編輯Dockerfile檔案通過docker b
從Windows訊息的角度看視窗應用程式的執行過程
一個典型的Win32視窗應用程式的框架是這樣的: 程式入口點(WinMain函式)-->註冊視窗類(呼叫RegisterClass函式或RegisterClassEx函式)-->建立主視窗(呼叫CreateWindow函式或CreateWindowEx