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 11

設計 REST API

資源、方法、狀態碼與 JSON 回應
Interaction實作導覽 30 minute(s)

Learning Objectives

  • 說明 API 與回傳 HTML 的網頁有什麼不同,以及為什麼需要 API
  • 用複數名詞設計資源網址,選對 HTTP 方法,說明什麼是冪等
  • 選對 200、201、204、400、404、409、422 這幾個狀態碼
  • 用 jsonify、get_json、Location 標頭寫出筆記的 CRUD API,並用 API 工具與 curl 操作

誰會來呼叫 API

到目前為止,view 函數回傳的都是 HTML:瀏覽器收到後畫成畫面。可是想讀這些資料的不只有瀏覽器:

  • 手機 App:畫面是 App 自己畫的,只需要資料
  • 前端框架或頁面裡的 JavaScript:想更新畫面的一小塊,不想換整頁(第 13 課)
  • 別的程式:排程腳本、其他網站的伺服器、AI Agent

這些呼叫者要的是結構化的資料,不是版面。所以另外開一組網址,回傳 JSON(JavaScript Object Notation,一種文字格式的資料,Python 的字典和串列可以直接對應)。這組網址就是 API(Application Programming Interface)。

REST 是一種常見的設計風格:把資料看成一個個資源(resource),每個資源有自己的網址,用 HTTP 方法表達要做什麼。REST 是風格,不是標準,沒有人會檢查你「夠不夠 REST」;重點是讓別人看到網址和方法就猜得到它做什麼。

逐步看:一個會回 JSON 的 API

和前幾課一樣,client = app.test_client() 扮演呼叫者。注意 create_note 回傳的三個東西,再看 client 那邊怎麼用 Location 繼續下一個請求。

資源命名與 HTTP 方法

網址用名詞表示資源,複數表示一群,動作交給 HTTP 方法:

方法與網址 意思 成功時的狀態碼
GET /api/notes 列出筆記 200
POST /api/notes 新增一則 201,加上 Location
GET /api/notes/3 讀第 3 則 200
PUT /api/notes/3 把第 3 則整筆換掉 200
PATCH /api/notes/3 只改第 3 則的部分欄位 200
DELETE /api/notes/3 刪除第 3 則 204,沒有內容
GET /api/courses/8/notes 第 8 門課底下的筆記(巢狀) 200

/api/getNote?id=3、/api/deleteNote/3 這種把動作寫進網址的做法,方法就失去意義了,別人也猜不到還有哪些動作。

冪等(idempotent):同一個請求送一次和送很多次,伺服器上的結果一樣。這在網路不穩時很實用:呼叫者沒收到回應,不知道伺服器處理了沒有,如果方法是冪等的,再送一次也不會出事。

依 RFC 9110,GET、PUT、DELETE 是冪等的,POST 不是。下面的動畫實際送兩次,比較 notes 的變化。

逐步看:同一個請求送兩次

每個請求都送兩次。請看輸出最右邊的 notes:哪一種方法送第二次時,伺服器上的資料又變了?

選對狀態碼

呼叫者的程式靠狀態碼決定下一步,所以「出錯還回 200」是常見的壞習慣:程式會以為成功了。這一課會用到的幾個:

狀態碼 名稱 什麼時候用
200 OK 成功,有內容
201 Created 新增成功,加上 Location
204 No Content 成功,沒有內容(例如刪除)
400 Bad Request 請求本身有問題,例如 JSON 語法錯誤
404 Not Found 這個資源不存在
405 Method Not Allowed 網址存在,但不接受這個方法(Flask 自動處理)
409 Conflict 和現有資料衝突,例如標題重複
422 Unprocessable Content(Flask 目前顯示舊名稱 Unprocessable Entity) 格式對,但內容不合規則,例如標題空白

400 和 422 的界線不是每個 API 都一樣,有些 API 全部回 400。同一個 API 裡保持一致比選哪一個更重要。

逐步看:同一個端點的四種結果

四個請求依序走到不同的 return。下一課會把這些檢查寫得更完整,這裡先看狀態碼怎麼分。

互動練習:操作筆記 API

這次沒有瀏覽器畫面,只有 API 工具:選方法、填網址與內容,按「送出」,直接看回應。上面一排按鈕會幫你填好常用的請求。

資料存在 SQLite(第 9 課的寫法),可以到「資料庫」面板確認。Location 這類回應標頭在「HTTP 請求與回應」面板。

在終端機用 curl 呼叫 API

API 工具做的事,在真實環境通常用 curl 或 Postman 這類工具。把上面的 app.py 存到自己的電腦,開兩個終端機,一個跑 flask --app app run,另一個照下面操作。

下面的輸出是實際執行的結果,日期和版本會依你的環境不同。指令是 macOS、Linux 的寫法;Windows 的 PowerShell 裡 curl 可能是別的指令,請寫 curl.exe,而且 JSON 裡的引號要另外跳脫,最簡單的做法是把內容存成檔案,用 -d @note.json 送出。

  1. 1

    新增一則筆記

    -X 指定方法,-H 加標頭,-d 是內容,-i 連回應標頭一起顯示。少了 Content-Type: application/json,get_json() 會回 415。

  2. 2

    讀、改、刪

    GET 是預設方法,不用寫 -X。DELETE 成功時沒有內容,所以用 -w 把狀態碼印出來。

這段導覽完成了

動手做:讀書會 API

補完三個地方,讓狀態碼、標頭與內容都正確。程式最下面的測試不要改。

你的輸出
預期輸出

想一想

1.

要設計「刪除第 3 則筆記」的 API,哪一種寫法最符合 REST 風格?

2.

下面的程式會印出什麼?

from flask import Flask, jsonify

app = Flask(__name__)

@app.post('/api/notes')
def create():
    return jsonify(id=1), 201, {'Location': '/api/notes/1'}

r = app.test_client().post('/api/notes')
print(r.status_code, r.headers.get('Location'))
3.

同一個 DELETE /api/notes/2 送兩次,第一次回 204,第二次回 404。這樣還算冪等嗎?

4.

使用者新增筆記時,標題和自己的另一則筆記重複,你決定不允許。最適合回哪個狀態碼?

5.

呼叫者送出 POST /api/notes 後網路斷了,沒收到回應。他可以直接再送一次嗎?

第 1 次作答

重點整理

  • API 回傳 JSON 這類結構化的資料,給程式讀,不是給瀏覽器畫
  • 網址用複數名詞表示資源,動作交給 HTTP 方法;同一個 API 的風格要一致
  • GET、PUT、DELETE 是冪等的,POST 不是;冪等看的是伺服器上的結果
  • 新增回 201 加 Location,刪除回 204,找不到回 404,衝突回 409,出錯不要回 200
  • jsonify 產生 JSON 回應,request.get_json() 讀請求內容

目前的 API 假設呼叫者都送對的東西:少了 title、型別不對、不是 JSON,或者打錯網址,回應都不夠好。下一課處理這些情況,再加上分頁。

這一課完成了!
Next lesson:API 的驗證、分頁與錯誤格式
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。