[ADP-401] HTTP 問題基礎
概述
錯誤回應是任何 API 的重要組成部分。顯然,HTTP 狀態碼對於錯誤(4xx,5xx)無法為 API 調用者提供足夠的資訊來準確理解發生了什麼。
過去我們可能會引入自定義錯誤代碼來描述標準 HTTP 狀態錯誤代碼以外的錯誤,但這並不清晰也不直觀。
目前 RFC 9457 提供了一種描述錯誤的標準方式,本 ADP 基於 RFC 9457 提供了一種設計錯誤回應的標準方法。
指導原則
- 錯誤回應必須 (MUST) 與 HTTP Problem 規範相容。
- 錯誤回應必須 (MUST) 採用 JSON 格式。
- 錯誤回應的 Content-Type 標頭必須 (MUST) 為
application/problem+json
。 - 問題類型設計請參見 ADP-403。
Q & A
可以針對所有 HTTP Problem 統一添加新欄位嗎?
- 理論上在 RFC 9457 中明確指出擴充欄位要針對問題類型。
- 如果有此需求(非特定問題類型而是對 HTTP Problem 做擴充),看是否需要通過添加 Header 來處理(metadata),或者提出設計變更到本 ADP。
客戶端對於 HTTP Problem 如何應對?
- 客戶端應該(SHOULD)根據
type
來處理問題。 - 客戶端應該(SHOULD)忽略它目前未知或與定義中格式不同的欄位。
- 客戶端應該(SHOULD)根據
哪些欄位是必要的?
- RFC 9457 並未明確定義哪些欄位為可選,參考 Zalando 的 Problem 後決定:
- 如有需要,可以 (MAY) 省略
detail
,甚至title
欄位,前提是type
與status
已足以說明問題。 type
為必要且最重要欄位,詳細設計請參考 ADP-403。status
亦為必要欄位,主要作為 HTTP Status Header 的轉譯,方便錯誤物件轉發處理。title
用於補充說明 type,若 type 已明確可省略。若支援內容協商,title 應 (SHOULD) 支援本地化。detail
建議 (SHOULD) 提供用戶修復問題的具體指示,應以口語化語言撰寫,並支援多語言(如有 Accept-Language)。detail
的重要性次於title
,若存在應聚焦於解決方法。
instance
在 Zandando Problem Schema 中為可選,參考後也定為可選,但建議採用程式化方式帶入此欄位(不需要手動在每個錯誤回應中填寫)。
- 如有需要,可以 (MAY) 省略
- RFC 9457 並未明確定義哪些欄位為可選,參考 Zalando 的 Problem 後決定:
多語言支援?
- 若 API 支援 Accept-Language,
detail
應 (SHOULD) 提供多語言內容。無論是否支援多語言,只要有detail
欄位,應 (SHOULD) 指定Content-Language
。
- 若 API 支援 Accept-Language,
錯誤回應結構
錯誤回應的範例:
http
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{
type: '/problems/predefined-type',
status: 500,
title: '簡短的人類可讀錯誤描述',
instance: '/items/12345',
detail: '指引使用者如何修復問題'
}
錯誤回應的基本結構應如下:
屬性 | 描述 | 必需 | 示例 |
---|---|---|---|
type(string, uri-reference) | 必須是一個 URI(提示1) | 是 | /problems/{problem-type} |
title(string) | 問題類型的簡短、人類可讀摘要 | 是 | "Validation Error" |
status(integer) | 原始伺服器為此問題實例生成的 HTTP 狀態碼 | 是 | 400 |
detail(string) | 特定於此問題實例的人類可讀解釋(提示2) | 否 | "提供的輸入無效,請提供正確格式。" |
instance(string, uri-reference) | 標識問題特定實例的 URI 引用 | 否 | "/shop-items/123456" |
提示1
理想情況下,這應該指向一個真實的網站,以提供更多關於該類型的資訊,正如 RFC 建議的那樣。
如果我們在引入問題類型時無法提供絕對 URI,使用相對 URI 是可以接受的。如:/problems/invalid-input
提示2 關於 ProblemJSON.detail
- 如果其他屬性中已有足夠的資訊,
detail
可以(MAY)為空。 detail
應為用戶提供如何修復問題的指示。也就是說,它應該(SHOULD)使用口語化的英語(或如果您的 API 支持 Accept-Language,則使用其他語言),如 RFC9457 中定義的那樣。detail
和title
的區別:title
是一個更通用的問題簡短描述,而detail
提供了修復問題的具體指示。- 如果您的 API 支持 Accept-Language,
detail
可能是多語言的。無論您是否支持多語言,只要有detail
屬性(property),就應該(SHOULD)指定Content-Language
。
設計思路
許多網站使用自定義錯誤系統來描述 HTTP 狀態碼以外的自定義錯誤,例如額外的基於數字的代碼。然而,用戶仍然需要從互聯網查詢代碼的含義,因為不同應用程序之間沒有標準。
示例
讓我們以 Windows 錯誤代碼為例。參見 https://learn.microsoft.com/zh-tw/windows-hardware/drivers/debugger/bug-check-code-reference2。
應該這樣做
json
{
"type": "/problems/ipt-watchdog-timeout",
"status": 500,
"detail": "...."
}
不應該這樣做
json
{
"status": "0x0001db"
}
401 Unauthorized 範例
以下為 401 Unauthorized(未授權)錯誤的 HTTP Problem 標準範例:
基本範例
json
{
"type": "/problems/unauthorized",
"title": "未授權的存取",
"status": 401,
"detail": "您必須先登入,才能存取此資源。"
}
帶有本地化與 instance 的範例
json
{
"type": "https://example.com/problems/unauthorized",
"title": "未授權的存取",
"status": 401,
"detail": "請登入您的帳號後重試。",
"instance": "/orders/123456"
}
進階範例
json
{
"type": "https://example.com/problems/unauthorized",
"title": "未授權的存取",
"status": 401,
"detail": "Access token 已過期,請重新登入取得新的授權。",
"instance": "/api/v1/profile"
}
參考
標準參考
設計參考
- https://api.otto.de/portal/guidelines/r000034
- https://swagger.io/blog/problem-details-rfc9457-api-error-handling/
- https://problems-registry.smartbear.com/
更新紀錄
2025.05.09
: 移除 extraType 設計,補充 Q&A 與範例區塊,潤飾用語。