National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 05

回應與請求勾點

狀態碼、標頭、轉址、abort、before_request 與 g
Interaction實作 25 minute(s)

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 函數直接取用。

互動練習:看回應,改勾點

每次操作後看「HTTP 請求與回應」面板的回應標頭,並切到「伺服器紀錄」看勾點印出來的順序。後面兩個任務要改程式。存檔後要自己按 ↻ 或點連結才算。

動手做:用勾點保護 API

完成三個地方:

  • check_token:路徑以 /api/ 開頭時,檢查標頭 X-Token 是否為 secret。不是的話回傳 {'error': '未授權'} 與 401;是的話把 g.user 設為 'amy'
  • add_header:每個回應都加上標頭 X-App: notes
  • item:item_id 大於 3 時用 abort(404)

程式最下面的測試不要改。

你的輸出
預期輸出

想一想

1.

下面的程式會印出什麼?

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('/')
2.

before_request 的函數回傳了 ('請先登入', 401),接下來會發生什麼?

3.

要確保資料庫連線不管發生什麼事都會關閉,應該放在哪裡?

4.

想把「目前使用者」從 before_request 傳給 view 函數,而且下一個請求要重新判斷,最適合放哪裡?

5.

return redirect('/login') 的預設狀態碼是?

第 1 次作答

重點整理

  • 回傳字串是 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 模板把它分開。

這一課完成了!
Next lesson:Jinja2 模板基礎
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。