目錄
02-3. HTTP 方法(GET、POST、PUT、DELETE)
⏱️ 閱讀時間: 12 分鐘 🎯 難度: ⭐⭐ (簡單)
🎯 本篇重點
深入理解 HTTP 各種方法的用途、差異、冪等性、安全性,以及 RESTful API 設計原則。
🤔 什麼是 HTTP 方法?
HTTP Method(HTTP 動詞) = 告訴伺服器你想做什麼操作
一句話解釋: HTTP 方法就像是對圖書館員說的「動詞」:借書(GET)、還書(POST)、換書(PUT)、退書(DELETE)。
📚 用圖書館操作來比喻 HTTP 方法
圖書館場景 HTTP 方法
查詢書籍資訊 GET /books/123
(只看不動) → 取得書籍資訊
借書(新增借閱記錄) POST /borrow
→ 建立新的借閱記錄
更新借閱資訊 PUT /borrow/456
(完整更換) → 更新整筆借閱記錄
延長借閱期限 PATCH /borrow/456
(部分修改) → 只更新到期日期
取消借閱 DELETE /borrow/456
→ 刪除借閱記錄
查詢圖書館有哪些服務 OPTIONS /
→ 查詢支援的方法
只問書在不在 HEAD /books/123
(不拿書) → 只取得標頭,不取內容🔍 HTTP 方法完整介紹
GET - 取得資源
用途:
- 讀取資料
- 查詢資訊
- 不修改伺服器資料
特性:
✅ 安全(Safe)- 不會修改資料
✅ 冪等(Idempotent)- 多次請求結果相同
✅ 可快取(Cacheable)
✅ 參數在 URL 中
範例:
GET /api/users/123 HTTP/1.1
Host: api.example.com
→ 取得用戶 123 的資料
GET /api/articles?page=1&limit=10 HTTP/1.1
Host: api.example.com
→ 取得文章列表(第 1 頁,每頁 10 筆)POST - 新增資源
用途:
- 建立新資源
- 提交表單
- 上傳檔案
- 執行操作
特性:
❌ 不安全(Not Safe)- 會修改資料
❌ 非冪等(Not Idempotent)- 多次請求可能建立多個資源
❌ 通常不可快取
✅ 參數在 Body 中
範例:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"username": "john",
"email": "john@example.com",
"password": "123456"
}
→ 建立新用戶
→ 每次執行都會建立一個新用戶
POST /api/login HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"username": "john",
"password": "123456"
}
→ 執行登入操作PUT - 完整更新資源
用途:
- 完整更新現有資源
- 如果資源不存在,可能建立新資源
特性:
❌ 不安全(Not Safe)- 會修改資料
✅ 冪等(Idempotent)- 多次請求結果相同
❌ 不可快取
✅ 必須提供完整資料
範例:
PUT /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"username": "john_updated",
"email": "john.new@example.com",
"age": 30,
"bio": "Updated bio"
}
→ 完整替換用戶 123 的所有資料
→ 必須提供所有欄位
→ 多次執行結果相同(冪等)
注意:
如果只傳部分欄位,其他欄位可能被清空!PATCH - 部分更新資源
用途:
- 部分更新現有資源
- 只修改特定欄位
特性:
❌ 不安全(Not Safe)- 會修改資料
✅ 可以是冪等(視實作而定)
❌ 不可快取
✅ 只需提供要修改的欄位
範例:
PATCH /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"email": "john.new@example.com"
}
→ 只更新 email 欄位
→ 其他欄位不受影響
PATCH /api/articles/456 HTTP/1.1
Content-Type: application/json
{
"title": "新標題",
"views": 100
}
→ 只更新 title 和 viewsDELETE - 刪除資源
用途:
- 刪除指定資源
特性:
❌ 不安全(Not Safe)- 會修改資料
✅ 冪等(Idempotent)- 多次刪除結果相同
❌ 不可快取
範例:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
→ 刪除用戶 123
HTTP/1.1 204 No Content
→ 成功刪除,無回應內容
第二次執行:
DELETE /api/users/123 HTTP/1.1
HTTP/1.1 404 Not Found
→ 資源已不存在(但仍然是冪等的)HEAD - 取得標頭
用途:
- 只取得回應標頭
- 不取得回應主體
- 檢查資源是否存在
- 檢查資源大小
特性:
✅ 安全(Safe)
✅ 冪等(Idempotent)
✅ 可快取
範例:
HEAD /api/files/large-video.mp4 HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 1073741824
Last-Modified: Mon, 06 Jan 2025 12:00:00 GMT
→ 只取得標頭,不下載 1GB 的影片
→ 可以先檢查檔案大小
用途:
- 檢查連結是否有效
- 取得檔案大小(Content-Length)
- 檢查資源是否被修改(Last-Modified)OPTIONS - 查詢支援的方法
用途:
- 查詢伺服器支援哪些 HTTP 方法
- CORS 預檢請求(Preflight)
特性:
✅ 安全(Safe)
✅ 冪等(Idempotent)
範例:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
→ 伺服器回傳支援的方法
CORS 預檢:
瀏覽器在發送跨域請求前,會先發送 OPTIONS 請求
確認伺服器是否允許跨域存取📊 HTTP 方法對比表
完整對比
| 方法 | 用途 | 安全 | 冪等 | 可快取 | Body |
|---|---|---|---|---|---|
| GET | 取得資源 | ✅ | ✅ | ✅ | ❌ |
| POST | 新增資源 | ❌ | ❌ | ❌ | ✅ |
| PUT | 完整更新 | ❌ | ✅ | ❌ | ✅ |
| PATCH | 部分更新 | ❌ | 可能 | ❌ | ✅ |
| DELETE | 刪除資源 | ❌ | ✅ | ❌ | ❌ |
| HEAD | 取得標頭 | ✅ | ✅ | ✅ | ❌ |
| OPTIONS | 查詢方法 | ✅ | ✅ | ❌ | ❌ |
安全性(Safety)
安全的方法:
- 不會修改伺服器資料
- 只是讀取
安全:GET、HEAD、OPTIONS
不安全:POST、PUT、PATCH、DELETE
範例:
GET /api/users/123 ✅ 安全(只讀取)
POST /api/users ❌ 不安全(建立新用戶)
DELETE /api/users/123 ❌ 不安全(刪除用戶)冪等性(Idempotence)
冪等 = 多次執行結果相同
冪等:GET、PUT、DELETE、HEAD、OPTIONS
非冪等:POST
範例 1:GET(冪等)
GET /api/users/123
→ 第 1 次:取得用戶 123 資料
→ 第 2 次:取得用戶 123 資料(相同)
→ 第 N 次:取得用戶 123 資料(相同)
✅ 冪等
範例 2:DELETE(冪等)
DELETE /api/users/123
→ 第 1 次:刪除成功(200 OK)
→ 第 2 次:資源不存在(404 Not Found)
→ 結果相同:用戶 123 不存在
✅ 冪等
範例 3:POST(非冪等)
POST /api/users
{"username": "john"}
→ 第 1 次:建立用戶 ID=123
→ 第 2 次:建立用戶 ID=124
→ 第 3 次:建立用戶 ID=125
❌ 非冪等(每次建立新資源)
範例 4:PUT(冪等)
PUT /api/users/123
{"username": "john", "age": 30}
→ 第 1 次:更新成功
→ 第 2 次:更新成功(資料相同)
→ 第 N 次:更新成功(資料相同)
✅ 冪等🎯 RESTful API 設計原則
REST = Representational State Transfer
核心概念:
1. 資源(Resource)
- 所有東西都是資源
- 用 URL 表示資源
2. HTTP 方法表示操作
- GET(讀取)
- POST(新增)
- PUT/PATCH(更新)
- DELETE(刪除)
3. 無狀態(Stateless)
- 每個請求獨立
- 不依賴 Session
4. 標準化回應
- 使用標準 HTTP 狀態碼
- JSON 格式RESTful API 設計範例
用戶管理 API
【取得所有用戶】
GET /api/users
→ 回應:[{id: 1, name: "John"}, {id: 2, name: "Mary"}]
【取得單一用戶】
GET /api/users/123
→ 回應:{id: 123, name: "John", email: "john@example.com"}
【建立新用戶】
POST /api/users
Body: {name: "John", email: "john@example.com"}
→ 回應:{id: 123, name: "John", email: "john@example.com"}
【完整更新用戶】
PUT /api/users/123
Body: {name: "John Updated", email: "john.new@example.com", age: 30}
→ 回應:{id: 123, name: "John Updated", ...}
【部分更新用戶】
PATCH /api/users/123
Body: {email: "john.new@example.com"}
→ 回應:{id: 123, name: "John", email: "john.new@example.com"}
【刪除用戶】
DELETE /api/users/123
→ 回應:204 No Content文章管理 API
【取得所有文章】
GET /api/articles
GET /api/articles?page=1&limit=10&sort=date
【取得單一文章】
GET /api/articles/456
【取得文章的評論】
GET /api/articles/456/comments
【建立新文章】
POST /api/articles
Body: {title: "標題", content: "內容"}
【在文章下新增評論】
POST /api/articles/456/comments
Body: {content: "很棒的文章!"}
【更新文章】
PUT /api/articles/456
Body: {title: "新標題", content: "新內容"}
【刪除文章】
DELETE /api/articles/456
【刪除評論】
DELETE /api/articles/456/comments/789RESTful 命名規則
✅ 好的設計:
GET /api/users - 取得用戶列表
GET /api/users/123 - 取得單一用戶
POST /api/users - 建立用戶
PUT /api/users/123 - 更新用戶
DELETE /api/users/123 - 刪除用戶
GET /api/articles/456/comments - 取得文章的評論
POST /api/articles/456/comments - 新增評論
❌ 不好的設計:
GET /api/getUsers - 動詞不應該在 URL 中
POST /api/createUser - 動詞應該用 HTTP 方法表示
GET /api/deleteUser/123 - 刪除應該用 DELETE
POST /api/users/123/update - 更新應該用 PUT/PATCH
原則:
1. 使用名詞,不用動詞
2. 複數形式(users 而不是 user)
3. 使用 HTTP 方法表示操作
4. 資源層級用 / 分隔
5. 小寫字母,用連字號 -(不用底線 _)💻 實戰範例
Python (Flask) 實作 RESTful API
from flask import Flask, request, jsonify
app = Flask(__name__)
# 模擬資料庫
users = {
1: {"id": 1, "name": "John", "email": "john@example.com"},
2: {"id": 2, "name": "Mary", "email": "mary@example.com"}
}
next_id = 3
# GET - 取得所有用戶
@app.route('/api/users', methods=['GET'])
def get_users():
return jsonify(list(users.values())), 200
# GET - 取得單一用戶
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = users.get(user_id)
if user:
return jsonify(user), 200
else:
return jsonify({"error": "User not found"}), 404
# POST - 建立新用戶
@app.route('/api/users', methods=['POST'])
def create_user():
global next_id
data = request.get_json()
new_user = {
"id": next_id,
"name": data.get("name"),
"email": data.get("email")
}
users[next_id] = new_user
next_id += 1
return jsonify(new_user), 201
# PUT - 完整更新用戶
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
if user_id not in users:
return jsonify({"error": "User not found"}), 404
data = request.get_json()
users[user_id] = {
"id": user_id,
"name": data.get("name"),
"email": data.get("email")
}
return jsonify(users[user_id]), 200
# PATCH - 部分更新用戶
@app.route('/api/users/<int:user_id>', methods=['PATCH'])
def partial_update_user(user_id):
if user_id not in users:
return jsonify({"error": "User not found"}), 404
data = request.get_json()
if "name" in data:
users[user_id]["name"] = data["name"]
if "email" in data:
users[user_id]["email"] = data["email"]
return jsonify(users[user_id]), 200
# DELETE - 刪除用戶
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
if user_id not in users:
return jsonify({"error": "User not found"}), 404
del users[user_id]
return '', 204
if __name__ == '__main__':
app.run(debug=True)測試 API
# GET - 取得所有用戶
curl http://localhost:5000/api/users
# GET - 取得單一用戶
curl http://localhost:5000/api/users/1
# POST - 建立新用戶
curl -X POST http://localhost:5000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
# PUT - 完整更新用戶
curl -X PUT http://localhost:5000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"John Updated","email":"john.new@example.com"}'
# PATCH - 部分更新用戶
curl -X PATCH http://localhost:5000/api/users/1 \
-H "Content-Type: application/json" \
-d '{"email":"john.updated@example.com"}'
# DELETE - 刪除用戶
curl -X DELETE http://localhost:5000/api/users/1JavaScript (Fetch API) 呼叫
// GET - 取得所有用戶
fetch('http://localhost:5000/api/users')
.then(response => response.json())
.then(data => console.log(data));
// GET - 取得單一用戶
fetch('http://localhost:5000/api/users/1')
.then(response => response.json())
.then(data => console.log(data));
// POST - 建立新用戶
fetch('http://localhost:5000/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
// PUT - 完整更新用戶
fetch('http://localhost:5000/api/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John Updated',
email: 'john.new@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
// PATCH - 部分更新用戶
fetch('http://localhost:5000/api/users/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'john.updated@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
// DELETE - 刪除用戶
fetch('http://localhost:5000/api/users/1', {
method: 'DELETE'
})
.then(response => {
if (response.status === 204) {
console.log('刪除成功');
}
});🎓 面試常見問題
Q1:PUT 和 PATCH 有什麼差異?
A:PUT 是完整替換,PATCH 是部分更新
PUT(完整替換):
- 必須提供所有欄位
- 沒提供的欄位會被清空或設為預設值
- 完全替換整個資源
範例:
原始資料:
{
"id": 123,
"name": "John",
"email": "john@example.com",
"age": 30,
"bio": "Hello"
}
PUT /api/users/123
{
"name": "John Updated",
"email": "john.new@example.com"
}
結果:
{
"id": 123,
"name": "John Updated",
"email": "john.new@example.com",
"age": null, ← 被清空
"bio": null ← 被清空
}
PATCH(部分更新):
- 只需提供要修改的欄位
- 沒提供的欄位保持不變
- 只更新部分資源
範例:
原始資料:
{
"id": 123,
"name": "John",
"email": "john@example.com",
"age": 30,
"bio": "Hello"
}
PATCH /api/users/123
{
"email": "john.new@example.com"
}
結果:
{
"id": 123,
"name": "John", ← 保持不變
"email": "john.new@example.com", ← 更新
"age": 30, ← 保持不變
"bio": "Hello" ← 保持不變
}
實務建議:
- 更新少數欄位:用 PATCH(效率高)
- 需要完整替換:用 PUT
- 大多數情況用 PATCH 比較好Q2:POST 和 PUT 有什麼差異?
A:POST 新增資源,PUT 更新資源
POST:
- 建立新資源
- 伺服器決定資源 ID
- 非冪等(多次執行建立多個資源)
- 通常回傳 201 Created
範例:
POST /api/users
{"name": "John"}
→ 建立用戶 ID=123
再執行一次:
POST /api/users
{"name": "John"}
→ 建立用戶 ID=124(非冪等)
PUT:
- 更新現有資源(或建立指定 ID 的資源)
- 客戶端指定資源 ID
- 冪等(多次執行結果相同)
- 通常回傳 200 OK
範例:
PUT /api/users/123
{"name": "John"}
→ 更新用戶 123
再執行一次:
PUT /api/users/123
{"name": "John"}
→ 更新用戶 123(結果相同,冪等)
特殊情況(PUT 建立資源):
PUT /api/users/999
{"name": "Alice"}
如果用戶 999 不存在:
→ 建立用戶 999(客戶端指定 ID)
→ 回傳 201 Created
但這種用法較少見,通常還是用 POST 建立資源Q3:為什麼 DELETE 是冪等的?
A:因為多次刪除的「最終狀態」相同
冪等的定義:
多次執行後,資源的狀態相同
DELETE 的冪等性:
第 1 次:DELETE /api/users/123
→ 用戶 123 被刪除(200 OK 或 204 No Content)
→ 資源狀態:用戶 123 不存在 ✅
第 2 次:DELETE /api/users/123
→ 用戶 123 已經不存在(404 Not Found)
→ 資源狀態:用戶 123 不存在 ✅
第 N 次:DELETE /api/users/123
→ 用戶 123 還是不存在(404 Not Found)
→ 資源狀態:用戶 123 不存在 ✅
結論:雖然回應狀態碼不同(第一次 200,後續 404)
但資源的最終狀態相同(用戶 123 不存在)
→ 所以是冪等的
實作建議:
// 方法 1:回傳不同狀態碼
第 1 次 DELETE → 200 OK(刪除成功)
第 2 次 DELETE → 404 Not Found(資源不存在)
// 方法 2:都回傳成功(更符合冪等概念)
第 1 次 DELETE → 204 No Content
第 2 次 DELETE → 204 No Content(視為成功,因為資源確實不存在)
兩種方法都可以,但方法 2 更符合冪等的精神Q4:什麼時候該用 POST,什麼時候該用 GET?
A:根據操作性質和安全性決定
用 GET:
✅ 讀取資料(不修改)
✅ 查詢、搜尋
✅ 需要快取的請求
✅ 可以加入書籤的請求
✅ 可以被搜尋引擎索引
範例:
GET /api/users - 取得用戶列表
GET /api/users/123 - 取得單一用戶
GET /api/articles?q=keyword - 搜尋文章
GET /api/products?page=1 - 分頁查詢
用 POST:
✅ 建立資料
✅ 修改資料
✅ 執行操作
✅ 傳送敏感資訊
✅ 傳送大量資料
範例:
POST /api/users - 建立用戶
POST /api/login - 登入(執行操作)
POST /api/logout - 登出
POST /api/orders - 建立訂單
常見錯誤(❌ 不要這樣做):
❌ GET /api/deleteUser/123 - 刪除應該用 DELETE
❌ GET /api/createUser?name=John - 建立應該用 POST
❌ GET /api/login?username=john&password=123 - 敏感資訊用 POST
為什麼不能用 GET 傳送敏感資訊?
1. URL 會被記錄在瀏覽器歷史
2. URL 會被記錄在伺服器日誌
3. URL 可能被分享或洩露
4. URL 長度有限制
範例(危險):
GET /api/login?username=john&password=secret123
→ 密碼暴露在 URL 中
→ 會被記錄在多個地方
→ 極度不安全
正確做法:
POST /api/login
Body: {"username": "john", "password": "secret123"}
→ 密碼在 Body 中
→ HTTPS 加密
→ 安全Q5:RESTful API 一定要遵守嗎?
A:不一定,但遵守有很多好處
RESTful 的優點:
✅ 統一標準(容易理解)
✅ 可預測性(知道 GET /users 是取得用戶列表)
✅ 工具支援好(自動產生文件、客戶端)
✅ 快取友善(GET 請求可快取)
✅ 團隊協作容易(大家遵守同一規則)
不遵守 REST 的情況:
有些操作不適合 CRUD:
範例 1:複雜的查詢
❌ GET /api/users?age_gt=18&age_lt=30&city=Taipei&sort=name
(URL 太複雜)
✅ POST /api/users/search
Body: {
"age": {"min": 18, "max": 30},
"city": "Taipei",
"sort": "name"
}
範例 2:複雜的操作
❌ PUT /api/orders/123/status
(更新訂單狀態,但可能有複雜邏輯)
✅ POST /api/orders/123/cancel
✅ POST /api/orders/123/ship
✅ POST /api/orders/123/deliver
(明確表達意圖)
範例 3:批次操作
✅ POST /api/users/batch-delete
Body: {"ids": [1, 2, 3, 4, 5]}
範例 4:RPC 風格的 API
✅ POST /api/calculate-shipping-fee
Body: {"from": "Taipei", "to": "Kaohsiung", "weight": 5}
實務建議:
1. 簡單的 CRUD 操作:遵守 RESTful
2. 複雜的業務邏輯:可以用 RPC 風格
3. 團隊統一:選定一種風格,全團隊遵守
4. 優先考慮可讀性和維護性
現代替代方案:
- GraphQL(更靈活的查詢)
- gRPC(高效能 RPC)
- WebSocket(雙向即時通訊)✅ 重點回顧
HTTP 方法用途:
- GET - 讀取資源(安全、冪等、可快取)
- POST - 新增資源(不安全、非冪等)
- PUT - 完整更新(不安全、冪等)
- PATCH - 部分更新(不安全、可能冪等)
- DELETE - 刪除資源(不安全、冪等)
- HEAD - 只取標頭(安全、冪等)
- OPTIONS - 查詢方法(安全、冪等)
核心概念:
- 安全性 - 不修改資料(GET、HEAD、OPTIONS)
- 冪等性 - 多次執行結果相同(GET、PUT、DELETE)
- 可快取 - 可以被快取(GET、HEAD)
PUT vs PATCH:
- PUT = 完整替換(需要所有欄位)
- PATCH = 部分更新(只需修改的欄位)
POST vs PUT:
- POST = 新增資源(伺服器決定 ID,非冪等)
- PUT = 更新資源(客戶端指定 ID,冪等)
RESTful API 設計:
- ✅ 使用名詞(/users 而不是 /getUsers)
- ✅ 複數形式(/users 而不是 /user)
- ✅ HTTP 方法表示操作
- ✅ 資源層級用 / 分隔
- ✅ 統一的回應格式
面試重點:
- ✅ PUT(完整替換)vs PATCH(部分更新)
- ✅ POST(新增)vs PUT(更新)
- ✅ DELETE 的冪等性(最終狀態相同)
- ✅ GET 不應用於敏感資訊或修改操作
- ✅ RESTful 有優點但不是絕對
上一篇: 02-2. HTTP 請求與回應 下一篇: 02-4. HTTP 狀態碼完整指南
最後更新:2025-01-06