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 和 views

DELETE - 刪除資源

用途:
- 刪除指定資源

特性:
❌ 不安全(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/789

RESTful 命名規則

✅ 好的設計:
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/1

JavaScript (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

0%