1 學習目标
- 能夠說出裝飾器路由實作的幾個關鍵的類
- 能夠說出實作 HTTP 狀态保持的原理
- 能夠說出 Flask 各個上下文對象
- 能夠說出 Flask-Script 擴充的作用
2 裝飾器路由具體實作梳理
Flask有兩大核心:Werkzeug和Jinja2
- Werkzeug 實作路由、調試和Web伺服器網關接口
- Jinja2 實作了模闆。
Werkzeug是一個遵循WSGI協定的python函數庫
- 其内部實作了很多Web架構底層的東西,比如request和response對象;
- 與WSGI規範的相容;支援Unicode;
- 支援基本的會話管理和簽名Cookie;
- 內建URL請求路由等。
Werkzeug庫的 routing 子產品負責實作 URL 解析。不同的 URL 對應不同的視圖函數,routing子產品會對請求資訊的URL進行解析,比對到URL對應的視圖函數,執行該函數以此生成一個響應資訊。
routing子產品内部有:
- Rule類:用來構造不同的URL模式的對象,路由URL規則
- Map類:存儲所有的URL規則和一些配置參數
- BaseConverter的子類:負責定義比對規則
- MapAdapter類:負責協調Rule做具體的比對的工作
3 擷取請求的參數
3.1 request
request 就是flask中代表目前請求的 request 對象,其中一個請求上下文變量(了解成全局變量,在視圖函數中直接使用可以取到目前本次請求)
常用的屬性如下:
屬性 | 說明 | 類型 |
---|---|---|
data | 記錄請求的資料,并轉換為字元串 | * |
form | 記錄請求中的表單資料 | MultiDict |
args | 記錄請求中的查詢參數 | MultiDict |
cookies | 記錄請求中的cookie資訊 | Dict |
headers | 記錄請求中的封包頭 | EnvironHeaders |
method | 記錄請求使用的HTTP方法 | GET/POST |
url | 記錄請求的URL位址 | string |
files | 記錄請求上傳的檔案 | * |
查詢字典args,就是url?問号後邊跟的資料
3.2 示例
擷取上傳的圖檔并儲存到本地
@app.route('/', methods=['POST'])
def index():
pic = request.files.get('pic') # pic 為上傳時的 key 值
pic.save('aaa.png')
return 'success'
注意:這裡必須限制請求方式是post,因為上傳檔案必須是表單post送出
接下來上傳圖檔:我們通過postman來上傳圖檔:
點選send之後,就可以看到視圖函數傳回的success:
查詢上傳結果:工程目錄下多了一個圖檔
3.3 端口暫用問題
3.4 request.data例子
新增加data函數:
@app.route("/data", methods=["POST"])
def data():
data = request.data
print(data)
return "success"
發起請求:選擇raw資料類型(原始資料類型)
控制台列印:
上圖中的b,代表的是資料類型 byte
4 狀态保持
4.1 cookie
因為 http 是一種無狀态協定,浏覽器請求伺服器是無狀态的。
- 無狀态:指一次使用者請求時,浏覽器、伺服器無法知道之前這個使用者做過什麼,每次請求都是一次新的請求。
- 無狀态原因:浏覽器與伺服器是使用 socket 套接字進行通信的,伺服器将請求結果傳回給浏覽器之後,會關閉目前的 socket 連接配接,而且伺服器也會在處理頁面完畢之後銷毀頁面對象。
有時需要保持下來使用者浏覽的狀态,比如使用者是否登入過,浏覽過哪些商品等
實作狀态保持主要有兩種方式:
- 在用戶端存儲資訊使用
Cookie
- 在伺服器端存儲資訊使用
Session
無狀态協定:
- 協定對于事務處理沒有記憶能力
- 對同一個 url 請求沒有上下文關系
- 每次的請求都是獨立的,它的執行情況和結果與前面的請求和之後的請求是無直接關系的,它不會受前面的請求應答情況直接影響,也不會直接影響後面的請求應答情況
- 伺服器中沒有儲存用戶端的狀态,用戶端必須每次帶上自己的狀态去請求伺服器
- 人生若隻如初見
狀态舉例:
有狀态:無狀态:
- A:你今天中午吃的啥?
- B:吃的大盤雞。
- A:味道怎麼樣呀?
- B:還不錯,挺好吃的。
是以需要cookie這種東西:
- A:你今天中午吃的啥?
- B:吃的大盤雞。
- A:味道怎麼樣呀?
- B:???啊?啥?啥味道怎麼樣?
- A:你今天中午吃的啥?
- B:吃的大盤雞。
- A:你今天中午吃的大盤雞味道怎麼樣呀?
- B:還不錯,挺好吃的。
4.1.1 Cookie 介紹
Cookie:指某些網站為了辨識使用者身份、進行會話跟蹤而儲存在使用者本地的資料(通常經過加密)。
- 複數形式Cookies。
- Cookie最早是網景公司的前雇員Lou Montulli在1993年3月的發明。
- Cookie是由伺服器端生成,發送給用戶端浏覽器,浏覽器會将Cookie的key/value儲存,下次請求同一網站時就發送該Cookie給伺服器(前提是浏覽器設定為啟用cookie)。
- Cookie的key/value可以由伺服器端自己定義。
應用:
- 最典型的應用是判定注冊使用者是否已經登入網站,使用者可能會得到提示,是否在下一次進入此網站時保留使用者資訊以便簡化登入手續,這些都是Cookie的功用。
- 網站的廣告推送,經常遇到通路某個網站時,會彈出小視窗,展示我們曾經在購物網站上看過的商品資訊。
- 購物車,使用者可能會在一段時間内在同一家網站的不同頁面中選擇不同的商品,這些資訊都會寫入Cookie,以便在最後付款時提取資訊。
提示:
- Cookie是存儲在浏覽器中的一段純文字資訊,建議不要存儲敏感資訊如密碼,因為電腦上的浏覽器可能被其它人使用
- Cookie基于域名安全,不同域名的Cookie是不能互相通路的
- 如通路itcast.cn時向浏覽器中寫了Cookie資訊,使用同一浏覽器通路baidu.com時,無法通路到itcast.cn寫的Cookie資訊
- 浏覽器的同源政策
- 當浏覽器請求某網站時,會将本網站下所有Cookie資訊送出給伺服器,是以在request中可以讀取Cookie資訊
4.1.2 cookie流程分析圖
cookie:一般使用者儲存使用者的登入狀态(user_id , user_name)
4.1.3 設定cookie
@app.route("/login")
def login():
# 相當于 return "success"
response = make_response("success")
return response
上圖代碼說明:
通過make_response(“響應體”)建立response響應對象,然後傳回
與直接return “響應體” 是一樣的
但是這裡我們需要用到response響應對象去設定cookie,是以需要這樣寫
通路:
用 response 響應對象設定 cookie 代碼:
@app.route("/login")
def login():
response = make_response("success")
response.set_cookie("user_id", "1")
response.set_cookie("user_name", "laowang")
return response
再次通路,在響應頭中有set-cookie:
4.1.4 擷取cookie
如果此時我們再通路目前網站的任何url,浏覽器都會通過 request 攜帶 cookie,傳遞給伺服器。如下:
擷取cookie代碼:
@app.route("/")
def index():
user_id = request.cookies.get("user_id")
user_name = request.cookies.get("user_name")
return "index: %s ---> %s" %(user_id, user_name)
通路:
檢視cookie:
點選上圖中的正在使用2個,進入下圖:
上圖中的到期時間是關閉浏覽器就到期
那我們如何設定過期時間呢?
4.1.5 設定 cookie 過期時間
@app.route("/login")
def login():
response = make_response("success")
response.set_cookie("user_id", "1", max_age=3600) # max_age的機關為秒
response.set_cookie("user_name", "langwang", max_age=3600) # 3600 即1小時
return response
4.1.6 删除 cookie
代碼如下:
@app.route("/logout")
def logout():
response = make_response("success")
response.delete_cookie("user_id")
response.delete_cookie("user_name")
return response
通路:
注:删除cookie 的實質其實就是給max_age設定為0
建立時間與過期時間一緻即為删除:如下圖
4.2 session
session:請求上下文對象,用于處理 http 請求中的一些資料内容
對于敏感、重要的資訊,建議要存儲在伺服器端,不能存儲在浏覽器中,如使用者名、餘額、等級、驗證碼等資訊
在伺服器端進行狀态保持的方案就是
Session 依賴于 Cookie
Session
4.2.1 session流程分析圖
4.2.2 session資料的設定
代碼:
@app.route("/login")
def login():
# 假裝校驗成功
session["user_id"] = "1"
session["user_name"] = "zhangsan"
return "success"
需要導入: from flask import session
通路:
發現報錯,設定session的時候需要設定SECRET_KEY,因為要進行加密(内容可以是任意字元串)
記得設定secret_key:
app.secret_key = 'asdfghjkl'
secret_key的作用:https://segmentfault.com/q/1010000007295395
再次通路:
這裡就多了一個設定cookie的操作,我們設定的session為啥這裡設定有cookie呢?這是因為伺服器自動設定的,并且這個cookie叫做sessionid,值還是加密後的值
4.2.3 session資料的擷取
代碼:
@app.route("/")
def index():
user_id = session["user_id"]
user_name = session["user_name"]
return "%s ---> %s" %(user_id, user_name)
通路:
通路的時候,浏覽器通過請求攜帶着cookie到服務端,服務端就可以根據這個sessionid找到對應的session資料
4.2.4 删除 session
代碼:
@app.route("/logout")
def logout():
session.pop("user_id")
session.pop("user_name")
return "success"
pop()函數的作用:
- 删除key對應的session
- 将key對應的session值傳回
通路:
4.2.5 代碼優化
再次通路首頁(或者通路"/logout"),擷取session(或者删除session),發現報錯:沒有這個key
其實這裡可以優化一下,即使沒有這個key,也不至于報錯,代碼如下:
from flask import Flask, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "asdfghjkl"
@app.route("/")
def index():
# 如果為空會報錯:builtins.KeyError
# user_id = session["user_id"]
# user_name = session["user_name"]
# 如果為空,就用“”代替
user_id = session.get("user_id", "")
user_name = session.get("user_name", "")
return "%s ---> %s" %(user_id, user_name)
@app.route("/login")
def login():
# 假裝校驗成功
session["user_id"] = "1"
session["user_name"] = "zhangsan"
return "success"
@app.route("/logout")
def logout():
# 如果為空會報錯:builtins.KeyError
# session.pop("user_id")
# session.pop("user_name")
# 如果為空,就傳回 None
session.pop("user_id", None)
session.pop("user_name", None)
return "success"
if __name__ == '__main__':
app.run(debug=True)
5 上下文
上下文:相當于一個容器,儲存了 Flask 程式運作過程中的一些資訊。
Flask中有兩種上下文:請求上下文和應用上下文
5.1 請求上下文(request context)
思考:在視圖函數中,如何取到目前請求的相關資料?比如:請求位址,請求方式,cookie等等
在 flask 中,可以直接在視圖函數中使用 request 這個對象進行擷取相關資料,而 request 就是請求上下文的對象,儲存了目前本次請求的相關資料,請求上下文對象有:request、session
requestsession
- 封裝了HTTP請求的内容,針對的是http請求。舉例:user = request.args.get('user'),擷取的是get請求的參數。
- 用來記錄請求會話中的資訊,針對的是使用者資訊。舉例:session['name'] = user.id,可以記錄使用者資訊。還可以通過session.get('name')擷取使用者資訊。
5.2 請求上下文示範
5.2.1 請求上下文 request
報錯如下:
5.2.2 請求上下文session
報錯如下:
注意:
這裡報錯還是說是請求上下文,因為session也是請求上下文的一種(隻有請求發起才會産生session)
5.3 應用上下文(application context)
它的字面意思是應用上下文,但它不是一直存在的,它隻是request context 中的一個對 app 的代理(人),所謂local proxy(本地代理)。它的作用主要是幫助 request 擷取目前的應用,它是伴 request 而生,随 request 而滅的。
應用上下文對象有:current_app,g
5.3.1 current_app
應用程式上下文,用于存儲應用程式中的變量,可以通過current_app.name列印目前app的名稱,也可以在current_app中存儲一些變量,例如:
- 應用的啟動腳本是哪個檔案,啟動時指定了哪些參數
- 加載了哪些配置檔案,導入了哪些配置
- 連了哪個資料庫
- 有哪些public的工具類、常量
- 應用跑再哪個機器上,IP多少,記憶體多大
current_app.name
current_app.test_value='value'
5.3.2 g變量
g 作為 flask 程式全局的一個臨時變量,充當者中間媒介的作用,我們可以通過它傳遞一些資料,g 儲存的是目前請求的全局變量,不同的請求會有不同的全局變量,通過不同的thread id差別
g.name='abc'
注意:不同的請求,會有不同的全局變量
5.4 應用上下文current_app
報錯資訊如下:
5.5 兩者差別:
- 請求上下文:儲存了用戶端和伺服器互動的資料
- 應用上下文:flask 應用程式運作過程中,儲存的一些配置資訊,比如程式名、資料庫連接配接、應用資訊等
上下文中的對象隻能在指定上下文中使用,超出範圍不能使用 請求上下文和應用上下文原理實作:https://segmentfault.com/a/1190000004223296
6 Flask-Script 擴充
通過使用Flask-Script擴充,我們可以在Flask伺服器啟動的時候,通過指令行的方式傳入參數。而不僅僅通過app.run()方法中傳參,比如我們可以通過:
python xxxxx.py runserver -host ip位址
以上代碼告訴伺服器在哪個網絡接口監聽來自用戶端的連接配接。預設情況下,伺服器隻監聽來自伺服器所在的計算機發起的連接配接,即localhost連接配接。
6.1 安裝插件
安裝 Flask-Script 擴充
pip install flask-script
6.2 Flask-Script
內建 Flask-Script,代碼:
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
# 需求:可以通過指令行在運作的時候指定運作的端口
# 把 Manager 類和應用程式執行個體進行關聯
# 将app交給manager進行管理:
manager = Manager(app)
@app.route("/")
def index():
return "index"
if __name__ == '__main__':
# app.run()
manager.run()
Flask-Script 還可以為目前應用程式添加腳本指令,後續項目中會使用到
運作:
添加shell相當于啟動一個python shell
runserver就是啟動flask項目
如下:
我們可以通過python hello.py runserver --help來檢視參數。
添加端口和調試:
再次運作:
6.3 pycharm 運作
剛剛點右鍵使用pycharm運作不了,那怎麼也能使用pycharm運作呢?
如下:
在script parameters出添加參數即可:
注:還可以添加 -d (相當于 debug = True)
此時點選右鍵運作:
就可以了: