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 03

路由與動態網址

網址裡的變數、型別轉換、HTTP 方法與 url_for
Interaction實作 25 minute(s)

Learning Objectives

  • 用 <name>、<int:id> 等轉換器從網址取得變數,並說明不符合時的結果
  • 用 methods、@app.get、@app.post 限制 HTTP 方法,區分 404 與 405
  • 說明結尾斜線的行為與路由比對的優先順序
  • 用 url_for 產生網址,說明為什麼比寫死網址好
  • 說明直接把使用者輸入放進 HTML 的風險,以及 escape 的作用

一個函數處理很多網址

討論區有上萬則貼文,不可能替每一則寫一個函數。實際上的做法是:網址 /note/1、/note/2⋯⋯都交給同一個函數,由網址裡的數字決定要顯示哪一則。

Flask 用角括號標出網址裡會變動的部分,比對到的值會當作引數傳給 view 函數:

@app.route('/note/<int:note_id>')
def note(note_id):            # 參數名稱要和角括號裡的一樣
    return f'第 {note_id} 則'

角括號裡可以寫轉換器(converter),指定接受什麼內容、傳進來是什麼型別:

轉換器 接受 傳入的型別
string(預設) 不含斜線的文字 str
int 正整數與 0 int
float 帶小數點的正數 float
path 和 string 一樣,但可以含斜線 str
uuid UUID 字串 uuid.UUID

網址不符合轉換器時,這條路由就不算比對到,所以結果是 404,view 函數不會被呼叫。

逐步看:轉換器比對成功與失敗

程式依序請求十個網址。看哪些會跳進 view 函數,哪些直接得到 404。呼叫堆疊裡看得到傳進來的引數值和型別。

HTTP 方法與結尾斜線

路由預設只接受 GET(Flask 會順便處理 HEAD 與 OPTIONS)。要接受其他方法,用 methods 參數,或用簡寫:

@app.route('/notes', methods=['GET', 'POST'])   # 同一個函數處理兩種方法
@app.get('/about')                              # 等於 methods=['GET']
@app.post('/vote')                              # 等於 methods=['POST']

網址存在但方法不對時,回應是 405 Method Not Allowed,和 404 不同:404 是沒有這個網址,405 是有這個網址,但不接受這個方法。

結尾斜線有兩種寫法,效果不一樣:

路由寫法 請求 /projects 請求 /projects/
/projects/(像資料夾) 轉址到 /projects/ 200
/projects(像檔案) 200 404

這樣同一個頁面只有一個標準網址。

逐步看:405 與結尾斜線

client.post、client.delete 可以送出不同方法的請求。注意 request.method 的值,以及回應的標頭。

url_for:用函數名稱產生網址

網頁裡到處都是連結。如果每個連結都寫死 /member/Amy,哪天網址改成 /people/Amy,就要找遍整個專案。url_for 用端點名稱(預設就是函數名稱)產生網址,只要改路由一個地方:

url_for('note', note_id=3)                   # /note/3
url_for('note', note_id=3, tab='reply')      # /note/3?tab=reply

路由裡沒有的參數會變成查詢字串。空白、中文這類特殊字元也會自動編碼。

路由比對的順序不是看誰寫在前面:比較明確的路由優先,固定的文字優先於變數。所以 /member/me 和 /member/<name> 可以並存,請求 /member/me 一定走前者。

逐步看:url_for 的結果

url_for 要知道目前在處理哪個請求才能用。view 函數裡本來就在請求之中;這裡在函數外面,所以用 test_request_context() 製造一個。

使用者輸入不能直接放進 HTML

網址是使用者可以任意輸入的。view 函數回傳的字串預設會被瀏覽器當成 HTML,所以這樣寫有風險:

@app.route('/hi/<name>')
def hi(name):
    return f'你好,{name}'      # 有風險:name 裡的標籤會被瀏覽器執行

如果有人把連結做成 /hi/<img src=x onerror=...> 傳給別人,點開的人瀏覽器就會執行裡面的 JavaScript,這叫做 XSS(跨站腳本攻擊)。解決方法是用 markupsafe.escape 把特殊字元換成無害的文字:

from markupsafe import escape
return f'你好,{escape(name)}'   # <b> 會變成 &lt;b&gt;,畫面上就是字面的 <b>

第 6 課的 Jinja2 模板預設會自動做這件事,第 17 課會完整談 XSS。

互動練習:比對路由、修 XSS、改網址

實際操作看看:哪些網址比對得到、方法不對時會怎樣,再修一個漏洞,最後體會寫死連結的問題。任務要依序做。存檔後要自己按 ↻ 或點連結才算。

動手做:課程的三個路由

替三個函數加上路由:

  1. course:網址 /courses/<編號>,編號只接受整數
  2. join:網址 /courses/<編號>/join,只接受 POST,回傳內容與狀態碼 201
  3. link:網址 /courses/<編號>/link,用 url_for 產生 course 的網址,並帶上查詢字串 tab=notes

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

你的輸出
預期輸出

想一想

1.

路由是 /note/<int:note_id>,請求 /note/abc 會得到什麼?

2.

下面的程式會印出什麼?

from flask import Flask
app = Flask(__name__)

@app.post('/vote')
def vote():
    return 'ok'

client = app.test_client()
print(client.get('/vote').status_code, client.get('/votes').status_code)
3.

下面的程式會印出什麼?

from flask import Flask, url_for
app = Flask(__name__)

@app.route('/note/<int:note_id>')
def show(note_id):
    return ''

with app.test_request_context():
    print(url_for('show', note_id=5, page=2))
4.

同時有 /member/<name> 和 /member/me 兩條路由,請求 /member/me 時會發生什麼?

5.

為什麼 view 函數不應該直接回傳 f'你好,{name}'(name 來自網址)?

第 1 次作答

重點整理

  • <int:id> 這類轉換器取出網址裡的值並轉型;不符合就是 404
  • 路由預設只接受 GET;methods、@app.get、@app.post 指定方法;方法不對是 405
  • /a/ 像資料夾,沒有斜線時會轉址;/a 像檔案,多了斜線是 404
  • 固定路徑優先於變數,和書寫順序無關
  • 用 url_for 產生網址,改路由時連結會跟著改
  • 使用者輸入放進 HTML 前要跳脫

網址能帶的資料有限。下一課看 request 物件,讀取查詢字串、表單、JSON 與標頭。

這一課完成了!
Next lesson:讀取請求的內容
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。