python flask源码解析_浅谈flask源码之请求过程
Flask
Flask是什么?
Flask是一個(gè)使用 Python 編寫(xiě)的輕量級(jí) Web 應(yīng)用框架, 讓我們可以使用Python語(yǔ)言快速搭建Web服務(wù), Flask也被稱為 "microframework" ,因?yàn)樗褂煤?jiǎn)單的核心, 用 extension 增加其他功能
為什么選擇Flask?
我們先來(lái)看看python現(xiàn)在比較流行的web框架
Flask
Django
Tornado
Sanic
Flask: 輕, 組件間松耦合, 自由、靈活,可擴(kuò)展性強(qiáng),第三方庫(kù)的選擇面廣的同時(shí)也增加了組件間兼容問(wèn)題
Django: Django相當(dāng)于一個(gè)全家桶, 幾乎包括了所有web開(kāi)發(fā)用到的模塊(session管理、CSRF防偽造請(qǐng)求、Form表單處理、ORM數(shù)據(jù)庫(kù)對(duì)象化、模板語(yǔ)言), 但是相對(duì)應(yīng)的會(huì)造成一個(gè)緊耦合的情況, 對(duì)第三方插件不太友好
Tornado: 底層通過(guò)eventloop來(lái)實(shí)現(xiàn)異步處理請(qǐng)求, 處理效率高, 學(xué)習(xí)難度大, 處理稍有不慎很容易阻塞主進(jìn)程導(dǎo)致不能正常提供服務(wù), 新版本也支持asyncio
Sanic: 一個(gè)類Flask框架, 但是底層使用uvloop進(jìn)行異步處理, 可以使用同步的方式編寫(xiě)異步代碼, 而且運(yùn)行效率十分高效.
WSGI
先來(lái)看看維基百科對(duì)WSGI的定義
Web服務(wù)器網(wǎng)關(guān)接口(Python Web Server Gateway Interface,縮寫(xiě)為WSGI)是為Python語(yǔ)言定義的Web服務(wù)器和Web應(yīng)用程序或框架之間的一種簡(jiǎn)單而通用的接口.
何為網(wǎng)關(guān), 即從客戶端發(fā)出的每個(gè)請(qǐng)求(數(shù)據(jù)包)第一個(gè)到達(dá)的地方, 然后再根據(jù)路由進(jìn)行轉(zhuǎn)發(fā)處理. 而對(duì)于服務(wù)端發(fā)送過(guò)來(lái)的消息, 總是先通過(guò)網(wǎng)關(guān)層, 然后再轉(zhuǎn)發(fā)至客戶端
那么可想而知, WSGI其實(shí)是作為一個(gè)網(wǎng)關(guān)接口, 來(lái)接受Server傳遞過(guò)來(lái)的信息, 然后通過(guò)這個(gè)接口調(diào)用后臺(tái)app里的view function進(jìn)行響應(yīng).
先看一段有趣的對(duì)話:
Nginx:Hey, WSGI, 我剛收到了一個(gè)請(qǐng)求,我需要你作些準(zhǔn)備, 然后由Flask來(lái)處理這個(gè)請(qǐng)求.
WSGI:OK, Nginx. 我會(huì)設(shè)置好環(huán)境變量, 然后將這個(gè)請(qǐng)求傳遞給Flask處理.
Flask:Thanks. WSGI給我一些時(shí)間,我將會(huì)把請(qǐng)求的響應(yīng)返回給你.
WSGI:Alright, 那我等你.
Flask:Okay, 我完成了, 這里是請(qǐng)求的響應(yīng)結(jié)果, 請(qǐng)求把結(jié)果傳遞給Nginx.
WSGI:Good job! Nginx, 這里是響應(yīng)結(jié)果, 已經(jīng)按照要求給你傳遞回來(lái)了.
Nginx:Cool, 我收到了, 我把響應(yīng)結(jié)果返回給客戶端.大家合作愉快~
對(duì)話里面可以清晰了解到WSGI、nginx、Flask三者的關(guān)系
下面來(lái)看看Flask中的wsgi接口(注意:每個(gè)進(jìn)入Flask的請(qǐng)求都會(huì)調(diào)用Flask.__call__)
# 摘自Flask源碼 app.py
class Flask(_PackageBoundObject):
# 中間省略
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
# environ: 一個(gè)包含全部HTTP請(qǐng)求信息的字典, 由WSGI Server解包HTTP請(qǐng)求生成
# start_response: WSGI Server提供的函數(shù), 調(diào)用可以發(fā)送響應(yīng)的狀態(tài)碼和HTTP報(bào)文頭,
# 函數(shù)在返回前必須調(diào)用一次.
:param environ: A WSGI environment.
:param start_response: A callable accepting a status code,
a list of headers, and an optional exception context to
start the response.
# 創(chuàng)建上下文
ctx = self.request_context(environ)
error = None
try:
try:
# 把上下文壓棧
ctx.push()
# 分發(fā)請(qǐng)求
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
# 返回結(jié)果
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 上下文出棧
ctx.auto_pop(error)
wsgi_app中定義的就是Flask處理一個(gè)請(qǐng)求的基本流程,
1.創(chuàng)建上下文
2.把上下文入棧
3.分發(fā)請(qǐng)求
4.上下文出棧
5.返回結(jié)果
其中response = self.full_dispatch_request()請(qǐng)求分發(fā)的過(guò)程我們需要關(guān)注一下
# 摘自Flask源碼 app.py
class Flask(_PackageBoundObject):
# 中間省略
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
return self.view_functions[rule.endpoint](**req.view_args)
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while handling an error')
return response
我們可以看到, 請(qǐng)求分發(fā)的操作其實(shí)是由dispatch_request來(lái)完成的, 而在請(qǐng)求進(jìn)行分發(fā)的前后我們可以看到Flask進(jìn)行了如下操作:
1.try_trigger_before_first_request_functions, 首次處理請(qǐng)求前的操作,通過(guò)@before_first_request定義,可以進(jìn)行數(shù)據(jù)庫(kù)連接
2.preprocess_request, 每次處理請(qǐng)求前進(jìn)行的操作, 通過(guò)@before_request來(lái)定義, 可以攔截請(qǐng)求
3.process_response, 每次正常處理請(qǐng)求后進(jìn)行的操作, 通過(guò)@after_request來(lái)定義, 可以統(tǒng)計(jì)接口訪問(wèn)成功的數(shù)量
4.finalize_request, 把視圖函數(shù)的返回值轉(zhuǎn)換成一個(gè)真正的響應(yīng)對(duì)象
以上的這些是Flask提供給我們使用的鉤子(hook), 可以根據(jù)自身需求來(lái)定義,
而hook中還有@teardown_request, 是在每次處理請(qǐng)求后執(zhí)行(無(wú)論是否有異常), 所以它是在上下文出棧的時(shí)候被調(diào)用
如果同時(shí)定義了四種鉤子(hook), 那么執(zhí)行順序應(yīng)該是
graph LR
before_first_request --> before_request
before_request --> after_request
after_request --> teardown_request
在請(qǐng)求函數(shù)和鉤子函數(shù)之間,一般通過(guò)全局變量g實(shí)現(xiàn)數(shù)據(jù)共享
現(xiàn)在的處理流程就變?yōu)?
1.創(chuàng)建上下文
2.上下文入棧
3.執(zhí)行before_first_request操作(如果是第一次處理請(qǐng)求)
4.執(zhí)行before_request操作
5.分發(fā)請(qǐng)求
6.執(zhí)行after_request操作
7.執(zhí)行teardown_request操作
8.上下文出棧
9.返回結(jié)果
其中3-7就是需要我們完成的部分.
如何使用Flask
上面我們知道, Flask處理請(qǐng)求的步驟, 那么我們來(lái)試試
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def before_first_request():
print('before_first_request run')
@app.before_request
def before_request():
print('before_request run')
@app.after_request
def after_request(param):
print('after_request run')
return param
@app.teardown_request
def teardown_request(param):
print('teardown_request run')
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
當(dāng)運(yùn)行flask進(jìn)程時(shí), 訪問(wèn)127.0.0.1:5000, 程序輸出, 正好認(rèn)證了我們之前說(shuō)的執(zhí)行順序.
before_first_request run
before_request run
after_request run
teardown_request run
127.0.0.1 - - [03/May/2018 18:42:52] "GET / HTTP/1.1" 200 -
路由分發(fā)
看了上面的代碼, 我們可能還是會(huì)有疑問(wèn), 為什么我們的請(qǐng)求就會(huì)跑到hello world 函數(shù)去處理呢?我們先來(lái)普及幾個(gè)知識(shí)點(diǎn):
url: 客戶端訪問(wèn)的網(wǎng)址
view_func: 即我們寫(xiě)的視圖函數(shù)
rule: 定義的匹配路由的地址
url_map: 存放著rule與endpoint的映射關(guān)系
endpoint: 可以看作為每個(gè)view_func的ID
view_functions: 一個(gè)字典, 以endpoint為key, view_func 為value
添加路由的方法:
1.@app.route
2.add_url_rule
我們先來(lái)看看@app.route干了什么事情
# 摘自Flask源碼 app.py
class Flask(_PackageBoundObject):
# 中間省略
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
我們可以看到, route函數(shù)是一個(gè)裝飾器, 它在執(zhí)行時(shí)會(huì)先獲取endpoint, 然后再通過(guò)調(diào)用add_url_rule來(lái)添加路由, 也就是說(shuō)所有添加路由的操作其實(shí)都是通過(guò)add_url_rule來(lái)完成的. 下面我們?cè)賮?lái)看看add_url_rule.
# 摘自Flask源碼 app.py
class Flask(_PackageBoundObject):
# 中間省略
# 定義view_functions
self.view_functions = {}
# 定義url_map
self.url_map = Map()
def add_url_rule(self, rule, endpoint=None, view_func=None,
provide_automatic_options=None, **options):
# 創(chuàng)建rule
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
# 把rule添加到url_map
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
# 把view_func 添加到view_functions字典
self.view_functions[endpoint] = view_func
可以看到, 當(dāng)我們添加路由時(shí), 會(huì)生成一個(gè)rule, 并把它存放到url_map里頭, 然后把view_func與其對(duì)應(yīng)的endpoint存到字典.
當(dāng)一個(gè)請(qǐng)求進(jìn)入時(shí), Flask會(huì)先根據(jù)用戶訪問(wèn)的Url到url_map里邊根據(jù)rule來(lái)獲取到endpoint, 然后再利用view_functions獲取endpoint在里邊所對(duì)應(yīng)的視圖函數(shù)
graph LR
url1 -->url_map
url2 -->url_map
url3 -->url_map
urln -->url_map
url_map --> endpoint
endpoint --> view_functions
上下文管理
下面我們?cè)賮?lái)看看之前一直忽略的上下文,什么是上下文呢?
上下文即語(yǔ)境、語(yǔ)意,是一句話中的語(yǔ)境,也就是語(yǔ)言環(huán)境. 一句莫名其妙的話出現(xiàn)會(huì)讓人不理解什么意思, 如果有語(yǔ)言環(huán)境的說(shuō)明, 則會(huì)更好, 這就是語(yǔ)境對(duì)語(yǔ)意的影響. 而對(duì)應(yīng)到程序里往往就是程序中需要共享的信息,保存著程序運(yùn)行或交互中需要保持或傳遞的信息.
Flask中有兩種上下文分別為:應(yīng)用上下文(AppContext)和請(qǐng)求上下文(RequestContext). 按照上面提到的我們很容易就聯(lián)想到:應(yīng)用上下文就是保存著應(yīng)用運(yùn)行或交互中需要保持或傳遞的信息, 如當(dāng)前應(yīng)用的應(yīng)用名, 當(dāng)前應(yīng)用注冊(cè)了什么路由, 又有什么視圖函數(shù)等. 而請(qǐng)求上下文就保存著處理請(qǐng)求過(guò)程中需要保持或傳遞的信息, 如這次請(qǐng)求的url是什么, 參數(shù)又是什么, 請(qǐng)求的method又是什么等.
我們只需要在需要用到這些信息的時(shí)候把它從上下文中取出來(lái)即可. 而上下文是有生命周期的, 不是所有時(shí)候都能獲取到.
上下文生命周期:
RequestContext: 生命周期在處理一次請(qǐng)求期間, 請(qǐng)求處理完成后生命周期也就結(jié)束了.
AppContext: 生命周期最長(zhǎng), 只要當(dāng)前應(yīng)用還在運(yùn)行, 就一直存在. (應(yīng)用未運(yùn)行前并不存在)
那么上下文是在什么時(shí)候創(chuàng)建的呢?我們又要如何創(chuàng)建上下文: 剛才我們提到, 在wsgi_app處理請(qǐng)求的時(shí)候就會(huì)先創(chuàng)建上下文, 那個(gè)上下文其實(shí)是請(qǐng)求上下文, 那應(yīng)用上下文呢?
# 摘自Flask源碼 ctx.py
class RequestContext(object):
# 中間省略
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# 獲取應(yīng)用上下文
app_ctx = _app_ctx_stack.top
# 判斷應(yīng)用上下文是否存在并與當(dāng)前應(yīng)用一致
if app_ctx is None or app_ctx.app != self.app:
# 創(chuàng)建應(yīng)用上下文并入棧
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
# 把請(qǐng)求上下文入棧
_request_ctx_stack.push(self)
我們知道當(dāng)有請(qǐng)求進(jìn)入時(shí), Flask會(huì)自動(dòng)幫我們來(lái)創(chuàng)建請(qǐng)求上下文. 而通過(guò)上述代碼我們可以看到,在創(chuàng)建請(qǐng)求上下文時(shí)會(huì)有一個(gè)判斷操作, 如果應(yīng)用上下文為空或與當(dāng)前應(yīng)用不匹配, 那么會(huì)重新創(chuàng)建一個(gè)應(yīng)用上下文. 所以說(shuō)一般情況下并不需要我們手動(dòng)去創(chuàng)建, 當(dāng)然如果需要, 你也可以顯式調(diào)用app_context與request_context來(lái)創(chuàng)建應(yīng)用上下文與請(qǐng)求上下文.
那么我們應(yīng)該如何使用上下文呢?
from flask import Flask, request, g, current_app
app = Flask(__name__)
@app.before_request
def before_request():
print 'before_request run'
g.name="Tom"
@app.after_request
def after_request(response):
print 'after_request run'
print(g.name)
return response
@app.route('/')
def index():
print(request.url)
g.name = 'Cat'
print(current_app.name)
if __name__ == '__main__':
app.run()
訪問(wèn)127.0.0.1:5000時(shí)程序輸出
before_request run
http://127.0.0.1:5000/
flask_run
after_request run
Cat
127.0.0.1 - - [04/May/2018 18:05:13] "GET / HTTP/1.1" 200 -
代碼里邊應(yīng)用到的current_app和g都屬于應(yīng)用上下文對(duì)象, 而request就是請(qǐng)求上下文.
current_app 表示當(dāng)前運(yùn)行程序文件的程序?qū)嵗?/p>
g: 處理請(qǐng)求時(shí)用作臨時(shí)存儲(chǔ)的對(duì)象. 每次請(qǐng)求都會(huì)重設(shè)這個(gè)變量 生命周期同RequestContext
request 代表的是當(dāng)前的請(qǐng)求
那么隨之而來(lái)的問(wèn)題是: 這些上下文的作用域是什么?
線程有個(gè)叫做ThreadLocal的類,也就是通常實(shí)現(xiàn)線程隔離的類. 而werkzeug自己實(shí)現(xiàn)了它的線程隔離類: werkzeug.local.Local. 而LocalStack就是用Local實(shí)現(xiàn)的.
這個(gè)我們可以通過(guò)globals.py可以看到
# 摘自Flask源碼 globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
_lookup_app_object思就是說(shuō), 對(duì)于不同的線程, 它們?cè)L問(wèn)這兩個(gè)對(duì)象看到的結(jié)果是不一樣的、完全隔離的. Flask通過(guò)這樣的方式來(lái)隔離每個(gè)請(qǐng)求.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
總結(jié)
以上是生活随笔為你收集整理的python flask源码解析_浅谈flask源码之请求过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据结构之ElemType(常用的数据网
- 下一篇: java中compile函数用法_【转】