Flask源碼淺析
前言
學習一樣東西,要先知其然,然後知其所以然。
這次,我們看看Flask Web框架的源碼。我會以Flask 0.1的源碼為例,把重點放在Flask如何處理請求上,看一看從一個請求到來到返回響應都經過了什麽過程。
你可能會問,為什麽以Flask 0.1為例啊,那都是好幾年前的一坨老代碼了?老,並不代表沒有用。相反,Flask 0.1的源碼設計極為精妙,包含了Flask的主幹部分,整個項目只有一個文件,六百行左右,分析起來也簡單,有利於我們了解整個Flask的脈絡。你可以從這裏來獲取Flask 0.1的源碼。
Flask中定義的幾個的類和函數
在Flask 0.1的源碼中,一共定義了五個類:
Request
Response
, 它們分別是Flask的請求和響應對象,分別繼承自Werkzeug中的請求和響應類_RequestContext
,請求上下文類。它包含了所有請求的相關信息。包括程序實例app,url匹配器,請求對象,session對象,g對象以及用於記錄閃現的消息的flashes
_RequestGlobals
,使用該類創建g對象,這個對象內沒有任何的屬性,你可以給該類的實例(即g)綁定任何的全局屬性。Flask
,它是整個Flask框架的中心類,它實現了WSGI程序用於處理請求和響應,並且,它是整個所有視圖函數、模板配置、URL規則的中心註冊處。
另外,Flask中還定義了一些函數:如render_template
url_for
、flash
、get_flashed_messages
等,相信大家都知道這些函數的作用,我就不在贅述。下面我們著重看看Flask
類。
Flask中本地上下文
在flask.py文件的最後,定義了幾個全局對象:
_request_ctx_stack = LocalStack() 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
是Flask的請求上下文堆棧,它的棧頂即是當前請求上下文對象的實例,當一個請求到來時,Flask會將一個請求上下文對象推入這個堆棧以便在程序中使用。current_app
、request
、session
和g
通過代理的方式從上下文堆棧中獲取到所需要的值。如果你還不清楚LocalStack
和LocalProxy
,可以參見什麽時Werkzeug
Flask類
下面,我們重點看一下Flask類是如何定義的。
從Flask類開頭和__init__
看起
在開始處,我們會看到Flask將Request
和Response
分別賦值給了request_class
和response_class
.
request_class = Request
response_class = Response
Flask並沒有在程序中直接使用Request
和Response
來生成請求和響應對象,而是通過調用request_class
和response_class
來生成,這就給我們自定義請求和響應類提供了方便。你可以通過繼承Flask中的請求和響應類來構建自己的請求和響應類,並將它們賦值給request_class
和response_class
即可。
在下面:
static_path = '/static'
secret_key = None
session_cookie_name = 'session'
我們可以看到在這裏Flask定義了靜態資源的目錄,密鑰以及cookie的名稱,當然,這些你也可以手動進行修改。
下面,我們看一看__init__
:
def __init__(self, package_name):
self.debug = False # 是否開啟調試
self.package_name = package_name
self.root_path = _get_package_path(self.package_name) # 程序的根目錄
self.view_functions = {} # 用於保存註冊的視圖函數
self.error_handlers = {} # 保存註冊的錯誤處理函數
self.before_request_funcs = [] # 保存請求開始時前調用的函數
self.after_request_funcs = [] # 保存請求完成後調用的函數
self.url_map = Map() # 保存路由規則
可以看到,我們用字典來保存註冊的視圖函數和錯誤處理函數,以及用列表保存請求前後要掉用的函數。其中用一個Map
對象url_map
來保存我們對URL進行處理的路由規則,其中每個路由規則為一個Rule
對象,我們會在下文看到。另外,__init__
中還定義的用於保存模板函數的屬性,以及Jinja2環境對象,這裏不在一一列出。
用於註冊視圖函數的裝飾器
眾所周知,我們可以使用下面的方式來註冊視圖函數:
@app.route('/')
def index():
return 'Hello World'
我們看一下這個裝飾器是如何實現的:
def route(self, rule, **options):
def decorator(f):
self.add_url_rule(rule, f.__name__, **options)
self.view_functions[f.__name__] = f
return f
return decorator
在route
中,會對我們傳入的視圖函數進行包裝,首先調用Flask中的add_url_rule
方法,然後以函數名為鍵,將視圖函數保存在__init__
中定義的用於保存視圖函數的view_functions
字典中。
下面,我們看看在add_url_rule(rule, f.__name__, **options)
內部發生了什麽:
def add_url_rule(self, rule, endpoint, **options):
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',)) # 默認監聽GET方法
self.url_map.add(Rule(rule, **options))
在add_url_rule
中,Flask首先將以‘endpoint‘
為鍵,將端點值放入options
中。如果options
沒有‘methods‘
鍵,Flask會在這裏給我們添加一個默認的GET方法,也就是說,當我們直接使用@app.route(‘/‘)
,而不傳入監聽的方法時,Flask會默認監聽GET方法。最後,Flask以當前的rule
和options
創建一個Rule
對象放入到url_map
中,為我們的程序新增了一條路由規則。
另外,除了route
裝飾器外,Flask中還有還提供了用於註冊錯誤函數、請求前調用的函數、請求後調用的函數等的裝飾器,這些裝飾器和route
裝飾器基本相同,只是沒有添加路由規則這個功能。例如請求處理前調用的函數的的裝飾器:
def before_request(self, f):
self.before_request_funcs.append(f)
return f
Flask請求響應流程
Flask中定義了wsgi_app(self, environ, start_response)
方法作為WSGI的程序,它並沒有寫死在__call__
方法中,因此可以為其添加中間件。當請求到來時,WSGI服務器會調用此方法,並將請求的參數和用於發起響應的函數作為參數傳遞給它。
def wsgi_app(self, 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)
Flask在with
語句下執行相關操作,這會觸發_RequestContext
中的__enter__
方法,從而推送請求上下文到堆棧中。在with
中,Flask先通過preprocess_request()
預處理請求,在preprocess_request()
中調用所有在beforce_request()
裝飾器中註冊的請求前要調用的函數。隨後,Flask使用dispatch_request()
來進行請求分發,獲得視圖函數的返回值或是錯誤處理器的返回值。然後Falsk將請求分發時獲得的返回值傳給make_response()
方法來生成一個響應對象,接下來,Flask在process_response()
方法中調用所有在after_request()
裝飾器中註冊的請求完成後要調用的函數。最後,通過response
來發起一個響應,這會自動調用start_response
方法來發起響應並將響應的值返回給WSGI服務器。
預處理請求
def preprocess_request(self):
for func in self.before_request_funcs:
rv = func()
if rv is not None:
return rv
上面的函數會在實際的請求分發之前調用,而且將會調用每一個使用before_request()
裝飾的函數。如果其中某一個函數返回一個值,這個值將會作為視圖返回值處理並停止進一步的請求處理。
請求分發
def dispatch_request(self):
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values)
except HTTPException, e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception, e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)
在上面的方法中,Flask對URL進行匹配獲取端點值和參數,然後調用相應的視圖函數並將視圖函數的返回值返回,或者返回相應的錯誤處理器的返回值。這裏的返回值不一定是響應對象,比如我們可以在視圖函數中返回一個字符串或者是使用render_template()
渲染好的模板,所以,為了能夠將返回值轉換成合適的對象,我們需要make_response()
方法來生成響應
生成響應
def make_response(self, rv):
"""
rv允許的類型如下所示:
======================= ===============================================
response_class 這個對象將被直接返回
str 使用這個字符串作為主體創建一個請求對象
unicode 將這個字符串進行utf-8編碼後作為主體創建一個請求對象
tuple 使用這個元組的內容作為參數創建一個請求對象
a WSGI function 這個函數將作為WSGI程序調用並緩存為響應對象
======================= ===============================================
:param rv: 視圖函數返回值
"""
if isinstance(rv, self.response_class):
return rv
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
在上面的方法中,也印證了我們上面所說的請求分發中視圖函數的返回值不一定是請求對象這一點。所以,我們在make_response
方法中對請求分發中獲取的返回值的類型進行判斷,通過不同的方式來創建真正的響應對象並返回。
響應處理
def process_response(self, response):
session = _request_ctx_stack.top.session
if session is not None:
self.save_session(session, response)
for handler in self.after_request_funcs:
response = handler(response)
return response
響應處理和預處理請求類似,都會循環調用所有註冊的請求後調用的函數來對響應對象response
進行處理,不過在此之前會先將session添加到響應對象中。
返回響應
我們Flask中的響應對象會繼承自Werkzeug中的Response
對象。Response
的實例可以根據傳入的參數,來發起一個特定的響應。你可以認為Response
是你可以創建的另一個標準的WSGI應用,這個應用可以根據你傳入的參數,來幫你做發起響應這件事。例如下面一個簡易的WSGI程序:
def application(environ, start_response):
request = Request(environ)
response = Response("Hello %s!" % request.args.get('name', 'World!'))
return response(environ, start_response)
好了,到此,Flask的一次請求就處理完了。不難發現,在Flask中,對Werzeug這個工具庫是很依賴的,從請求處理,路由匹配,到發起請求,都可見到Werkzeug的身影。
總結
請求響應類,請求上下文類,全局對象類,核心類Flask
Flask中,保存有視圖函數、錯誤處理函數、路由規則,可以處理請求
請求處理流程:預處理請求、請求分發、生成響應、返回響應
參考:
- 《Flask Web開發實戰》
Flask源碼淺析