I沾谓、請求鉤子
什么是請求鉤子?在客戶端和服務(wù)器交互的過程中戳鹅,有些準備工作或掃尾工作需要統(tǒng)一處理均驶,為了讓每個視圖函數(shù)避免編寫重復(fù)功能的代碼,flask提供了統(tǒng)一的接口可以添加這些處理函數(shù)枫虏,即請求鉤子妇穴。
比如:請求開始時的db_connect、auth認證隶债,或者結(jié)束時的置頂數(shù)據(jù)的交互格式腾它。
先回顧一下flask對請求的處理流程:
接收請求--》創(chuàng)建請求上下文--》請求上下文入棧--》創(chuàng)建該請求的應(yīng)用上下文--》應(yīng)用上下文入棧--》處理邏輯--》請求上下文出棧--》應(yīng)用上下文出棧
看了這個過程,flask放置請求鉤子的位置有:處理邏輯之前死讹,處理邏輯之后瞒滴,應(yīng)用上下文出棧之前。
那么為什么要設(shè)計請求鉤子呢赞警?
為了讓每個視圖函數(shù)避免編寫重復(fù)功能的代碼妓忍,F(xiàn)lask提供了通用設(shè)置等功能,即請求鉤子愧旦。請求鉤子是通過裝飾器的形式實現(xiàn)世剖,F(xiàn)lask支持五種請求鉤子:
1.before_first_request
在第一個請求前被調(diào)用(在處理第一個請求之前執(zhí)行),可在此方法內(nèi)做一些初始化操作
2.before_request
在每次請求前執(zhí)行笤虫,如果在某修飾的函數(shù)中返回了一個響應(yīng)(response)旁瘫,視圖函數(shù)將不再被調(diào)用。
3.after_request
在每次請求后(如果沒有拋出錯誤)執(zhí)行琼蚯,并且把視圖函數(shù)所生成的響應(yīng)傳入境蜕。可接收一個參數(shù):視圖函數(shù)做出的相應(yīng)凌停,在此函數(shù)中可以對響應(yīng)值在返回之前做最后一步修改處理粱年,需要將參數(shù)中的相應(yīng)在此參數(shù)中進行返回。在此方法中可對響應(yīng)做最后一步統(tǒng)一處理罚拟。
4.teardown_request
在每次請求后執(zhí)行台诗;接收一個參數(shù)(錯誤信息)完箩,如果有相關(guān)錯誤拋出,需要設(shè)置flask的配置DEBUG=False
拉队,teardown_request
才會接收到異常對象
5.teardown_appcontext
在應(yīng)用上下文從棧中彈出之前運行
首先創(chuàng)建一個新的app弊知,demo_gouzi.py并填充
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def before_first_request():
print('before_first_request')
@app.before_request
def before_request():
print('before_request')
@app.after_request
def after_request(response):
print('after_request')
response.headers['Content-Type'] = "application/json"
return response
@app.teardown_request
def teardown_request(response):
print('teardown_request')
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run(debug=False)
我們運行下服務(wù),觀察執(zhí)行結(jié)果
II粱快、request對象屬性
語法 | 描述 |
---|---|
request.scheme | 代表請求的方案,http或者https |
request.path | 請求的路徑 |
request.method | 表示請求使用的http方法,GET或者POST請求 |
request.encoding | 表示提交數(shù)據(jù)的編碼方式 |
request.GET | 獲取GET請求 |
request.POST | 獲取post的請求,比如前端提交的用戶密碼,可以通過request.POST.get()來獲取 另外:如果使用 POST 上傳文件的話秩彤,文件信息將包含在 FILES 屬性中 |
request.cookies | 包含所有的cookie |
request.session | 一個既可讀又可寫的類似于字典的對象,表示當(dāng)前的會話 |
III事哭、狀態(tài)保持
因為http是一種無狀態(tài)協(xié)議漫雷,不保持某一次請求所產(chǎn)生的信息,如想實現(xiàn)狀態(tài)保持鳍咱,在開發(fā)中解決方式有:
cookie:數(shù)據(jù)存儲在客戶端降盹,節(jié)省服務(wù)器空間,但是不安全
session:會話谤辜,數(shù)據(jù)存儲在服務(wù)器端
對于無狀態(tài)協(xié)議的理解蓄坏,看做以下五條
1、協(xié)議對于事務(wù)處理沒有記憶能力
2丑念、對同一個url請求沒有上下文關(guān)系
3涡戳、每次的請求都是獨立的,它的執(zhí)行情況和結(jié)果與前面的請求和之后的請求是無直接關(guān)系的脯倚,它不會受前面的請求應(yīng)答情況直接影響妹蔽,也不會直接影響后面的請求應(yīng)答情況
4、服務(wù)器中沒有保存客戶端的狀態(tài)挠将,客戶端必須每次帶上自己的狀態(tài)去請求服務(wù)器
5、人生若只如初見:比如商品加入購物車编整,重登后購物車里的東西無了
在Django中我們接觸過cookie和session舔稀,下面我們看看在Flask中如何使它們
1、cookie
cookie在flask中的使用掌测,我們以如下示例展示:
①設(shè)置cookie
# make_response相當(dāng)于Django中的http_response
from flask import make_response
@app.route('/cookie')
def set_cookie():
res = make_response('this is to set cookie')
res.set_cookie('username', 'laoma')
return res
啟動服務(wù)内贮,進入http://127.0.0.1:5000/cookie觀察結(jié)果
注:cookie是有時間限制的,超過時間會過期汞斧,我們可以自己設(shè)計cookie的時間(利用max_age
關(guān)鍵字)
# 設(shè)計cookie的時間夜郁,時間戳單位為:秒
res.set_cookie('username', 'laoma', max_age=3600)
②獲取cookie
from flask import request
@app.route('/request')
def get_cookie():
res = request.cookies.get('username')
return res
啟動服務(wù)觀察結(jié)果
2、session
session會話主要用于存儲敏感涉密信息粘勒,例如:個人信息竞端、賬戶余額、驗證碼等問題庙睡,session依賴于cookie事富,在TensorFlow中技俐,也會使用session盡管和web中的session有所不同,但也可理解為會話方式统台。
下面我們將使用session
from flask import Flask, redirect, url_for
from flask import session
# 需要設(shè)置secret_key
app.secret_key = '9999999'
@app.route('/index1')
def index1():
session['chase'] = '9999'
return redirect(url_for('index'))
# ‘0’為設(shè)置的默認值雕擂,默認值必須設(shè)置否則會報500錯誤
@app.route('/')
def index():
return session.get('chase', '0')
Session, Cookies以及一些第三方擴展都會用到SECRET_KEY
值,這是一個比較重要的配置值贱勃,應(yīng)該盡可能設(shè)置為一個很難猜到的值井赌,隨機值更佳。隨機的問題在于很難判斷什么是真隨機贵扰。一個密鑰應(yīng)該足夠隨機仇穗。你的操作系統(tǒng)可以基于一個密碼隨機生成器來生成漂亮的隨機值,這個值可以用來做密鑰拔鹰;
SECRET_KEY
配置變量是通用密鑰, 可在 Flask 和多個第三方擴展中使用. 如其名所示, 加密的強度取決于變量值的機密度. 不同的程序要使用不同的密鑰, 而且要保證其他人不知道你所用的字符串.其主要作用應(yīng)該是在各種加密過程中加鹽以增加安全性仪缸。在實際應(yīng)用中最好將這個參數(shù)存儲為系統(tǒng)環(huán)境變量。當(dāng)然我們也可以利用加密哈希列肢,就是讓cookie變得“安全”的字段恰画。服務(wù)器向我們發(fā)送最新的會話數(shù)據(jù)之前,會結(jié)合我們的會話數(shù)據(jù)瓷马、當(dāng)前時間戳以及服務(wù)器的私鑰來計算哈希從而加密session页眯,使得會話變得安全捉偏。
快速入門中的 “ 會話”部分對應(yīng)設(shè)置哪種服務(wù)器端機密提供了很好的建議。加密取決于機密;如果你沒有設(shè)置要使用的加密服務(wù)器端密碼雹食,那么每個人都可以破壞你的加密;就像你計算機的密碼一樣啥辨。秘密加上要簽名的數(shù)據(jù)用于創(chuàng)建簽名字符串介返,使用密碼哈希算法很難重新創(chuàng)建值;僅當(dāng)你具有完全相同的機密且原始數(shù)據(jù)時蒋伦,你才能重新創(chuàng)建此值弓摘,讓Flask檢測是否未經(jīng)許可對任何內(nèi)容進行了更改。由于Flask永遠不會將秘密包含在發(fā)送給客戶端的數(shù)據(jù)中痕届,因此客戶端無法篡改會話數(shù)據(jù)韧献,并希望產(chǎn)生新的有效簽名。
Flask盡量使用該itsdangerous庫來完成所有艱苦的工作研叫;會話使用帶有自定義JSON序列化程序的itsdangerous.URLSafeTimedSerializer類锤窑。
3、上下文
①請求上下文request context
request和session都屬于請求上下文對象嚷炉。request:封裝了HTTP請求的內(nèi)容渊啰,針對的是http請求。舉例:user = request.args.get('user')
申屹,獲取的是get請求的參數(shù)虽抄。
這不再做過多介紹
②應(yīng)用上下文application context
在發(fā)送請求時走搁,我們可以通過上下文發(fā)送全局對象,例如app.name
current_app和g都屬于應(yīng)用上下文對象迈窟。
1私植、current_app:表示當(dāng)前運行程序文件的程序?qū)嵗?br> 2、g:(global) 處理請求時车酣,用于臨時存儲的對象曲稼,每次請求都會重設(shè)這個變量。比如:我們可以獲取一些臨時請求的用戶信息湖员。當(dāng)調(diào)用
app = Flask(_name_)
的時候贫悄,創(chuàng)建了程序應(yīng)用對象app;
request 在每次http請求發(fā)生時娘摔,WSGI server調(diào)用Flask.call()
窄坦;然后在Flask內(nèi)部創(chuàng)建的request對象;
app的生命周期大于request和g凳寺,一個app存活期間鸭津,可能發(fā)生多次http請求,所以就會有多個request和g肠缨。
最終傳入視圖函數(shù)逆趋,通過return、redirect或render_template生成response對象晒奕,返回給客戶端闻书。
區(qū)別: 請求上下文:保存了客戶端和服務(wù)器交互的數(shù)據(jù)。 應(yīng)用上下文:在flask程序運行過程中脑慧,保存的一些配置信息魄眉,比如程序文件名、數(shù)據(jù)庫的連接闷袒、用戶信息等坑律。
一個操作實例:
from flask import current_app, g
@app.route('/')
def index():
print(current_app.name)
print(g.ip)
return session.get('chase', '0')
IV、上下文隔離原理
Flask主要是借助werkzueg來實現(xiàn)的霜运,其中請求之間隔離的方式借助了庫中Local、LocalStack蒋腮、LocalProxy 三個類淘捡,下面將逐一介紹
1、Local:
首先local添加了一個storage字典用來存儲添加的屬性池摧,當(dāng)給local對象進行添加屬性會自動根據(jù)get_ident方法獲取當(dāng)前的線程焦除、進程,鍵為線程作彤、進程ID號膘魄,值為添加的屬性乌逐,同理當(dāng)取值時,會自動判斷當(dāng)前的環(huán)境來取值, 類似于threading中的local
2创葡、LocalStack:
LocalStack基于local實現(xiàn)了一個‘先進晚出’的棧結(jié)構(gòu)浙踢,內(nèi)部通過為每個線程、進程添加
stack屬性來存儲數(shù)據(jù)灿渴,存儲數(shù)據(jù)的容器為list洛波,值得注意是每次添加值、取值得時候內(nèi)部會自動判斷當(dāng)前的線程骚露、進程環(huán)境為每個線程蹬挤、進程床架一個單獨的容器,這也是不同請求之間實現(xiàn)隔離的主要原因棘幸,同樣的current_app 當(dāng)前的應(yīng)用上下文也用到了類似的方式來管理焰扳,也就是說每個請求都有自己的應(yīng)用上下文,請求的每次入棧都會判斷當(dāng)前的應(yīng)用上下文误续,如果沒有創(chuàng)建應(yīng)用上下文吨悍,則創(chuàng)建對應(yīng)的上下文壓入棧也就是_app_ctx_stack,request對應(yīng)的則是_request_ctx_stack
3女嘲、LocalProxy:
作為一種代理模式的實現(xiàn)畜份,我們通過查看源碼發(fā)現(xiàn),LocalProxy實現(xiàn)了重寫了大量的類的‘魔術(shù)’方法欣尼,其實localproxy主要就是對對象爆雹、方法進行了包裝,通過localproxy進行過渡可以直接操作目標(biāo)對象LocalProxy對_lookup_req_object方法進行了包裝愕鼓,當(dāng)我們訪問request屬性的時候钙态,由于LocalProxy重寫了setattr getattr等特殊方法,在執(zhí)行的時候會調(diào)用_get_cuurent_object方法且直接執(zhí)行了self.__local()方法其實也就是包裝之前的_lookup_req_object菇晃,通過這個方法可以獲取全局請求上下文棧頂?shù)恼埱笊舷挛闹械膔equest屬性册倒,注意_request_ctx_stack中存儲的是請求上下文(Request_Context),我們每次操作的request其實是Request_Context的一個屬性
此處編輯匆忙磺送,存有大量借鑒驻子,而在此之后作者還會更新該內(nèi)容
上述內(nèi)容分別:
源碼解析轉(zhuǎn)載于:https://www.cnblogs.com/alplf123/p/10517057.html
流程圖轉(zhuǎn)載于:https://www.cnblogs.com/baijinshuo/p/10264326.html
V、初探模板 Jinja2
我們先建立一個flask_templates.py文件估灿,建立應(yīng)用來體驗一下Flask如何調(diào)用模板
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
index.html的內(nèi)容為
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
INDEX
</body>
</html>
我們啟動一下服務(wù)崇呵,看看模板是否被調(diào)用
我們來介紹一下Jinja2
Jinja2是Python下一個被廣泛應(yīng)用的模版引擎,他的設(shè)計思想來源于Django的模板引擎馅袁,并擴展了其語法和一系列強大的功能域慷。其中最顯著的一個是增加了沙箱執(zhí)行功能和可選的自動轉(zhuǎn)義功能,這對大多應(yīng)用的安全性來說是非常重要的。它基于unicode并能在python2.4之后的版本運行犹褒,包括python3(來源于百度百科)
其中Jinja2的模板語法與Django的模板內(nèi)容基本相似
在Jinja2中抵窒,存在三種語法:
1、控制結(jié)構(gòu){% %}
2叠骑、變量取值{{ }}
Jinja2模板中使用 {{ }} 語法表示一個變量李皇,它是一種特殊的占位符。當(dāng)利用Jinja2進行渲染的時候座云,它會把這些特殊的占位符進行填充/替換疙赠,Jinja2支持python中所有的Python數(shù)據(jù)類型比如列表、字段朦拖、對象等圃阳。
3、注釋{# #}
@app.route('/')
def index():
res = {'my_str': 'my', 'my_int': 1, 'my_list': [1, 2, 3], "my_dict": {"on": "day"}}
return render_template('index.html', my_str='my', my_int=1, my_list=[1, 2, 3], my_dict={"on": "day"})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Jinja2模板</h1>
{{ my_str }}<br>
{{ my_int }}<br>
{{ my_list }}<br>
{{ my_dict }}<br>
<hr>
my_int + 10 = {{ my_int + 10 }}<br>
my_int _ my_list[0] = {{ my_int + my_list[0] }}<br>
list[2] = {{ my_list.2 }}<br>
my_dict['on'] = {{ my_dict['on'] }}<br>
my_dict['on'] = {{ my_dict.on }}
</body>
</html>
啟動服務(wù)觀察結(jié)果璧帝,其語法與python內(nèi)的語法十分相似捍岳。