1. 程式人生 > >Flask 框架 - 檢視及路由 - 3

Flask 框架 - 檢視及路由 - 3

1 學習目標

  1. 能夠說出裝飾器路由實現的幾個關鍵的類

  2. 能夠說出實現 HTTP 狀態保持的原理

  3. 能夠說出 Flask 各個上下文物件

  4. 能夠說出 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

無狀態協議:

  1. 協議對於事務處理沒有記憶能力
  2. 對同一個 url 請求沒有上下文關係
  3. 每次的請求都是獨立的,它的執行情況和結果與前面的請求和之後的請求是無直接關係的,它不會受前面的請求應答情況直接影響,也不會直接影響後面的請求應答情況
  4. 伺服器中沒有儲存客戶端的狀態,客戶端必須每次帶上自己的狀態去請求伺服器
  5. 人生若只如初見

狀態舉例:

有狀態:

  • A:你今天中午吃的啥?
  • B:吃的大盤雞。
  • A:味道怎麼樣呀?
  • B:還不錯,挺好吃的。

無狀態:

  • A:你今天中午吃的啥?
  • B:吃的大盤雞。
  • A:味道怎麼樣呀?
  • B:???啊?啥?啥味道怎麼樣?

所以需要cookie這種東西:

  • 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

Session 依賴於 Cookie

 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()函式的作用:

  1. 刪除key對應的session
  2. 將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

request

  • 封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get('user'),獲取的是get請求的引數。

session

  • 用來記錄請求會話中的資訊,針對的是使用者資訊。舉例: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) 

此時點選右鍵執行:

就可以了: