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 78 24°C PM2.5 18
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 08

表單與驗證

接收表單、檢查輸入、重新顯示錯誤、送出後轉址
Interaction實作 30 minute(s)

Learning Objectives

  • 寫出 HTML 表單,並說明 method、action、name 的作用
  • 在同一個路由處理 GET(顯示表單)與 POST(處理資料)
  • 說明為什麼伺服器端驗證不可以省略,並在錯誤時保留使用者已填的內容
  • 說明 Post/Redirect/Get 解決的問題並實作
  • 用 flash 與 get_flashed_messages 顯示一次性的訊息

表單是使用者把資料交給伺服器的方式

報名、留言、登入,都是使用者填資料、送給伺服器。第 4 課看過伺服器怎麼讀 request.form,這一課處理前後的整個流程:畫出表單、檢查內容、出錯時讓使用者修改、成功後把頁面帶開。

<form method="post" action="/new">
  <input name="title">
  <button>送出</button>
</form>
屬性 作用
method 用哪個 HTTP 方法送出。沒有寫時是 GET,資料會放在網址上;會改變資料的動作用 post
action 送到哪個網址。沒有寫時送回目前的網址
name 欄位的名稱,也就是 request.form 裡的鍵。沒有 name 的欄位不會被送出

HTML 的細節可以參考 HTML 與 CSS 軌。

逐步看:同一個路由處理顯示與送出

/new 同時接受 GET 與 POST。程式送出四個請求:打開表單、送空白、送太長、送正確內容。模板 form.html 裡的輸入框有 required 和 maxlength,請看伺服器有沒有因此少做檢查。

前端驗證和伺服器端驗證的分工

HTML 的 required、maxlength、type="email" 這些屬性叫做前端驗證。它們的作用是讓使用者馬上知道哪裡填錯,不是安全機制。

前端驗證在使用者的瀏覽器裡執行,而瀏覽器歸使用者控制:可以用開發者工具把屬性拿掉,也可以不透過瀏覽器,直接用程式送出請求。MDN 明白寫著:前端驗證很容易繞過,伺服器一定要再檢查一次。

前端驗證 伺服器端驗證
目的 使用體驗 保護資料
能不能繞過 能 不能
可以省略嗎 可以,但使用者會不方便 不可以

欄位很多時,每個都手寫檢查會很冗長,可以看看 WTForms 這類套件。這一課先手寫,了解它們在做什麼。

互動練習:繞過表單,再把洞補起來

這個報名頁的輸入框有 required 和 maxlength="10",但伺服器完全沒有檢查。

這次除了模擬瀏覽器,畫面上還多了 API 工具:可以不經過網頁,直接把請求送給伺服器。按一下上方的預設按鈕會填好內容,再按「送出」。

這個練習在你自己瀏覽器裡的模擬網站上進行。對別人的網站做這類測試,要先取得授權。

重新整理為什麼會重複送出

瀏覽器按「重新整理」的意思是把最後一個請求再送一次。如果最後一個請求是 POST,就會重新送出表單:多一則留言、多扣一次款。瀏覽器會跳出確認視窗,但多數人看不懂,就直接按了確定。

解法是 Post/Redirect/Get(PRG):

  1. 瀏覽器送出 POST,伺服器處理資料
  2. 伺服器不回傳頁面,改回傳轉址(redirect,狀態碼 302)
  3. 瀏覽器自動送出 GET 打開新網址

這樣瀏覽器的最後一個請求是 GET,重新整理只是再讀一次頁面。

302 的規格沒有說方法一定要變,但瀏覽器收到 POST 的 302 回應時,下一個請求都會用 GET。要明確表達這個意思可以用 redirect(url, code=303),兩者在這裡的效果相同。

驗證失敗時不用轉址:要把錯誤訊息和使用者填的內容畫回表單,這時資料沒有寫入,重新送出也沒有副作用。

逐步看:兩種寫法,重新整理的結果

/bad 處理完直接回傳內容,/good 回傳轉址。程式用「再送一次最後的請求」來模擬按重新整理。

逐步看:flash 把訊息帶到下一頁

轉址之後是一個新的請求,view 函數裡的變數都不見了,那要怎麼告訴使用者「已送出」?flash 把訊息先存起來,下一個請求讀一次就清掉。

互動練習:親手按出重複留言,再修掉

這個留言板用串列保存資料,重新啟動伺服器就會清空,下一課才接資料庫。

請按瀏覽器的 ↻ 來重新整理。上一個請求是 POST 時,畫面會問要不要重新送出,和真的瀏覽器一樣。

動手做:課程回饋表單

完成 /feedback 的 POST 處理,順序是:

  1. title 是空的:error = '請填寫標題'
  2. score 不是 1 到 5 的整數:error = '分數要是 1 到 5'
  3. 都沒問題:把 (title, int(score)) 加進 items,flash('已收到'),轉址到 /thanks

程式最下面的測試不要改。注意 score 讀進來是字串,可能根本不是數字。

你的輸出
預期輸出

想一想

1.

下面的程式會印出什麼?

from flask import Flask, flash, get_flashed_messages
app = Flask(__name__)
app.secret_key = "dev"

@app.route("/save")
def save():
    flash("好")
    return ""

@app.route("/read")
def read():
    return str(len(get_flashed_messages()))

c = app.test_client()
c.get("/save")
print(c.get("/read").text, c.get("/read").text)
2.

沒有設定 secret_key 就呼叫 flash,會丟出什麼例外?(這段程式把 app.testing 設成 True,讓例外傳出來)

from flask import Flask, flash
app = Flask(__name__)
app.testing = True

@app.route("/")
def index():
    flash("好")
    return ""

app.test_client().get("/")
3.

表單的輸入框已經寫了 required,伺服器還需要檢查欄位是不是空的嗎?

4.

使用者送出留言後,伺服器回傳 redirect('/')。接著使用者按重新整理,瀏覽器會送出什麼?

5.

<input type="text" placeholder="姓名"> 放在表單裡,送出後 request.form 會有這個欄位嗎?

第 1 次作答

重點整理

  • 表單的 name 就是 request.form 的鍵;會改資料的動作用 method="post"
  • 同一個路由用 request.method 分開 GET 與 POST
  • 前端驗證是為了使用體驗,伺服器端驗證才是保護;出錯時把錯誤與使用者填的內容畫回去
  • 成功後回傳轉址(PRG),重新整理才不會重複送出
  • flash 把訊息帶到下一個請求,讀過一次就清掉;需要 secret_key,訊息不要太長,因為它存在 Cookie 裡

現在資料還放在串列裡,重新啟動伺服器就不見了。下一課接上資料庫。

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