Learning Objectives
- 用 tuple、make_response、jsonify、redirect、abort 控制狀態碼、標頭與內容
- 說出 before_request、view 函數、after_request、teardown_request 的執行順序
- 說明發生例外時 after_request 與 teardown_request 的差別
- 說明 g 的生命週期,以及 before_request 回傳值會中斷請求
view 函數的回傳值會變成回應
到目前為止,view 函數都只回傳字串。實際的網站還要控制更多事情:新增成功回 201、權限不足回 403、處理完表單轉到別頁、API 回 JSON。
Flask 依回傳值的型別決定怎麼做:
| 回傳 | 結果 |
|---|---|
| 字串 | 狀態碼 200,類型 text/html |
| 字典或串列 | 自動用 jsonify 轉成 JSON |
(內容, 狀態碼) |
指定狀態碼 |
(內容, 標頭) |
指定標頭 |
(內容, 狀態碼, 標頭) |
三個都指定 |
| 回應物件 | 直接使用,由 make_response、jsonify、redirect 產生 |
None |
錯誤:view 函數一定要回傳東西,否則是 500 |
另外兩個常用的工具:redirect(網址) 產生轉址回應,預設狀態碼 302;abort(狀態碼) 立刻結束這個請求,後面的程式不會執行。@app.errorhandler(404) 可以自訂錯誤頁,第 7 課會再介紹。
逐步看:各種回傳方式
程式依序請求八個網址,看每一個的狀態碼與類型。迴圈結束後再挑幾個,看回應裡的標頭。
請求勾點:每個請求都要做的事
有些事情每個請求都要做:確認登入狀態、連資料庫、記錄耗時。寫在每個 view 函數裡既重複又容易漏掉,所以 Flask 提供請求勾點(request hook):
請求進來
▼
before_request
▼
view 函數
▼
after_request
▼
回應交給伺服器
▼
teardown_request
before_request:有回傳值的話,直接當作回應,不進 view 函數after_request:收到回應物件,可以修改,一定要回傳回應teardown_request:用來收拾資源,參數是發生的例外(沒有就是None)
舊資料常提到 before_first_request(只在第一個請求前執行一次)。它在 Flask 2.2 棄用、2.3 已經移除,現在直接呼叫會出錯。官方的建議是:把初始化的程式寫在建立 app 的時候。
逐步看:勾點的順序與例外
四次請求:正常、abort、未處理的例外、測試模式下未處理的例外。看印出來的順序,以及 teardown_request 收到什麼。最後兩次的差別是這一段的重點。第三次請求的輸出會多一段 traceback,那是 Flask 寫進紀錄的錯誤,屬於正常現象。
after_request 與 teardown_request 怎麼選
after_request |
teardown_request |
|
|---|---|---|
| 收到什麼 | 回應物件 | 例外(沒有就是 None) |
| 可以改回應嗎 | 可以 | 不行,回應已經交給伺服器 |
| 例外往外丟時 | 不執行 | 仍然執行 |
| 適合做什麼 | 加標頭、記錄狀態碼 | 關閉資料庫連線、釋放資源 |
要改回應用 after_request;一定要做到的收尾用 teardown_request。另外有一個 teardown_appcontext,時機相近,第 9 課用它來關閉資料庫連線。
逐步看:g 與中斷請求
勾點和 view 函數是不同的函數,怎麼傳資料?用 g:一個讓你暫存資料的物件。它的生命週期和應用程式情境相同,一般就是一次請求,請求結束就消失。
這個範例在 before_request 認出使用者並放進 g,view 函數直接取用。
動手做:用勾點保護 API
完成三個地方:
check_token:路徑以/api/開頭時,檢查標頭X-Token是否為secret。不是的話回傳{'error': '未授權'}與 401;是的話把g.user設為'amy'add_header:每個回應都加上標頭X-App: notesitem:item_id大於 3 時用abort(404)
程式最下面的測試不要改。
想一想
下面的程式會印出什麼?
from flask import Flask
app = Flask(__name__)
@app.before_request
def a():
print('A')
@app.after_request
def b(resp):
print('B')
return resp
@app.route('/')
def v():
print('V')
return 'x'
app.test_client().get('/')
before_request 的函數回傳了 ('請先登入', 401),接下來會發生什麼?
要確保資料庫連線不管發生什麼事都會關閉,應該放在哪裡?
想把「目前使用者」從 before_request 傳給 view 函數,而且下一個請求要重新判斷,最適合放哪裡?
return redirect('/login') 的預設狀態碼是?
重點整理
- 回傳字串是 200 的 HTML;字典與串列轉成 JSON;tuple 可以指定狀態碼與標頭
make_response先做出回應物件再修改;redirect預設 302;abort立刻結束請求- 順序:
before_request→ view 函數 →after_request→teardown_request before_request有回傳值就不進 view 函數,適合放檢查- 例外往外丟時
after_request不執行,teardown 一定執行,所以收尾放 teardown g只活在一次請求,用來在勾點與 view 函數之間傳資料;before_first_request已移除
到這裡為止,HTML 都寫在 Python 字串裡,又難讀又容易出錯。下一課用 Jinja2 模板把它分開。