1. 概述
REST 是一種無狀態架構,客户端可以訪問和操作服務器上的資源。通常,RESTful 服務使用 HTTP 來宣佈它們管理的一組資源,並提供一個 API,允許客户端獲取或修改這些資源的當前狀態。
在本教程中,我們將學習處理 REST API 錯誤的一些最佳實踐,包括為用户提供相關信息的有用方法,來自大型網站的示例以及使用示例 Spring REST 應用程序的實際實現。
2. HTTP 狀態碼
當客户端向 HTTP 服務器發送請求,且服務器成功接收到該請求時,服務器必須通知客户端請求是否成功處理或未處理。
HTTP 通過五類狀態碼來實現這一點:
- 100-級別(指示性) – 服務器確認請求
- 200-級別(成功) – 服務器已按照預期完成請求
- 300-級別(重定向) – 客户端需要執行進一步操作以完成請求
- 400-級別(客户端錯誤) – 客户端發送了無效請求
- 500-級別(服務器錯誤) – 由於服務器端錯誤,未能滿足有效的請求
根據響應代碼,客户端可以推斷出特定請求的結果。
3. 處理錯誤
提供適當的狀態碼是處理錯誤的最初步驟。此外,我們可能還需要在響應體中提供更多信息。
3.1. 基本響應
處理錯誤的最簡單方法是使用適當的狀態碼進行響應。
以下是一些常見的響應碼:
- 400 Bad Request – 客户端發送了無效請求,例如缺少必需的請求主體或參數。
- 401 Unauthorized – 客户端未成功與服務器進行身份驗證。
- 403 Forbidden – 客户端已進行身份驗證,但沒有權限訪問請求的資源。
- 404 Not Found – 請求的資源不存在。
- 412 Precondition Failed – 請求頭字段中的一個或多個條件評估結果為 false。
- 500 Internal Server Error – 服務器在處理請求時發生了通用錯誤。
- 503 Service Unavailable – 請求的服務不可用。
雖然這些代碼是基礎的,但它們允許客户端了解發生的錯誤的大致性質。例如,如果收到 403 錯誤,我們知道我們缺乏訪問請求資源的權限。然而,在許多情況下,我們需要在響應中提供補充信息。
500 錯誤表明在處理請求時,服務器上發生了某些問題或異常。通常,此內部錯誤不屬於我們的客户端業務。
因此,為了儘量減少這些響應對客户端的影響,我們應該盡力處理或捕獲內部錯誤,並在可能的情況下使用其他適當的狀態碼。
例如,如果由於請求的資源不存在而發生異常,我們應該將其暴露為 404 錯誤,而不是 500 錯誤。
這並不意味着 500 永遠不應該返回,只是它應該用於意外條件——例如服務中斷——導致服務器無法執行請求時。
3.2. 默認 Spring 錯誤響應
這些原則過於普遍,Spring 已經將其編碼到默認的錯誤處理機制中。
為了説明,假設我們有一個 簡單的 Spring REST 應用,該應用管理書籍,並提供一個端點來根據 ID 檢索書籍:
curl -X GET -H "Accept: application/json" http://localhost:8082/spring-rest/api/book/1如果不存在 ID 為 1 的書籍,我們期望我們的控制器會拋出 BookNotFoundException。
對該端點執行 GET 請求,我們看到該異常被拋出,並且這是響應體:
{
"timestamp":"2019-09-16T22:14:45.624+0000",
"status":500,
"error":"Internal Server Error",
"message":"No message available",
"path":"/api/book/1"
}請注意,此默認錯誤處理程序包含錯誤發生的時間戳、HTTP 狀態碼、標題(error 字段)、如果啓用了默認錯誤中的消息則消息(默認情況下為空)以及錯誤發生的 URL 路徑。
這些字段為客户端或開發人員提供信息,以幫助他們解決問題,並且也構成標準錯誤處理機制中的幾個字段。
請注意,Spring 在我們的 BookNotFoundException 被拋出時,會自動返回 HTTP 狀態碼 500。雖然某些 API 會返回 500 狀態碼或其他通用狀態碼,正如我們將在 Facebook 和 Twitter API 中看到的那樣,為了簡化起見,儘可能使用最具體的錯誤代碼。
在我們的示例中,我們可以添加一個 @ControllerAdvice,以便當 BookNotFoundException 被拋出時,我們的 API 返回 404 狀態碼,以表示 未找到,而不是 500 內部服務器錯誤。
3.3. 更詳細的響應
如上所示的 Spring 示例中,有時狀態碼本身不足以展示錯誤的具體信息。當需要時,我們可以使用響應體向客户端提供額外信息。
在提供詳細響應時,我們應該包含:
- 錯誤 – 唯一的錯誤標識符
- 消息 – 簡短的人類可讀的消息
- 詳細信息 – 錯誤的更詳細的解釋
例如,如果客户端發送帶有錯誤的憑據的請求,我們可以發送以下 401 響應:
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct"
}錯誤字段不應與響應代碼匹配。相反,它應是特定於我們應用程序的唯一錯誤代碼。通常,錯誤字段沒有約定,除了它必須是唯一的。
通常,此字段僅包含字母數字字符和連接字符,例如短劃線或下劃線。例如,0001、auth-0001和incorrect-user-pass是唯一錯誤代碼的典範示例。
通常,請求體中的消息部分被認為是用户界面上可呈現的。因此,如果支持國際化,我們應將此標題翻譯。因此,如果客户端發送帶有與法語相對應的Accept-Language標頭請求,則標題值應翻譯為法語。
請求體中的詳細信息部分是供客户端開發人員使用的,而不是供最終用户使用的,因此不需要翻譯。
此外,我們還可以提供一個 URL——例如幫助字段——供客户端遵循以獲取更多信息:
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct",
"help": "https://example.com/help/error/auth-0001"
}有時,我們可能需要為單個請求報告多個錯誤。
在這種情況下,我們應該以列表的形式返回這些錯誤:
{
"errors": [
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct",
"help": "https://example.com/help/error/auth-0001"
},
...
]
}當發生單個錯誤時,我們會返回一個包含一個元素的列表。
請注意,響應多個錯誤可能會過於複雜,對於簡單的應用程序來説。在許多情況下,僅返回第一個或最重要的錯誤就足夠了。
3.4. 標準化響應體
雖然大多數 REST API 遵循相似的約定,但具體細節通常存在差異,包括字段名稱和響應體中包含的信息。這些差異使得庫和框架難以統一處理錯誤。
為了標準化 REST API 的錯誤處理,IETF 設計了 RFC 7807,它定義了一個通用的錯誤處理模式。
該模式由五部分組成:
- type – 一個 URI 標識符,用於分類錯誤
- title – 關於錯誤的簡短、可讀的描述
- status – HTTP 響應代碼(可選)
- detail – 錯誤的可讀性解釋
- instance – 一個 URI,用於標識錯誤的特定發生
與其使用我們自定義的響應體,我們可以將我們的響應體轉換為:
{
"type": "/errors/incorrect-user-pass",
"title": "Incorrect username or password.",
"status": 401,
"detail": "Authentication failed due to incorrect username or password.",
"instance": "/login/log/abc123"
}請注意,字段用於分類錯誤的類型,而 字段則標識錯誤在類似類和對象中發生的具體實例。
通過使用 URI,客户端可以遵循這些路徑以獲取有關錯誤的更多信息,類似於 HATEOAS 鏈接可以用於導航 REST API。
遵守 RFC 7807 是可選的,但如果需要保持一致性,則具有優勢。
4. 示例
上述實踐在許多流行的 REST API 中都有廣泛應用。雖然各個網站字段或格式的具體名稱可能存在差異,但總體模式幾乎是統一的。
4.1. 推特
以下代碼演示了在不提供所需身份驗證數據的情況下發送一個 GET 請求:
curl -X GET https://api.twitter.com/1.1/statuses/update.json?include_entities=trueTwitter API 返回以下錯誤信息:
{
"errors": [
{
"code":215,
"message":"Bad Authentication data."
}
]
}此響應包含一個包含單個錯誤的列表,其中包含錯誤代碼和消息。在Twitter的情況下,不存在詳細的消息,而是使用一個通用的錯誤——而不是更具體的401錯誤——來表示身份驗證失敗。
有時,使用更通用的狀態碼會更方便,如我們在下面的Spring示例中看到的。這允許開發人員捕獲異常組,而無需區分應返回的狀態碼。但是,儘可能使用最具體的狀態碼。
4.2. Facebook
類似於 Twitter,Facebook 的 Graph REST API 也包含詳細信息在響應中。
讓我們執行一個 POST 請求來與 Facebook Graph API 進行身份驗證:
curl -X GET https://graph.facebook.com/oauth/access_token?client_id=foo&client_secret=bar&grant_type=baz我們收到以下錯誤:
{
"error": {
"message": "Missing redirect_uri parameter.",
"type": "OAuthException",
"code": 191,
"fbtrace_id": "AWswcVwbcqfgrSgjG80MtqJ"
}
}類似於 Twitter,Facebook 也使用通用的錯誤碼——而不是更具體的 400 級別錯誤——來表示失敗。除了包含消息和數字代碼外,Facebook 還包含一個 字段,用於對錯誤進行分類,以及一個跟蹤 ID (fbtrace_id),它作為內部支持標識符。
5. 結論
本文探討了 REST API 錯誤處理的最佳實踐:
- 提供具體的 HTTP 狀態碼
- 在響應體中包含額外信息
- 以統一的方式處理異常
雖然錯誤處理的具體細節會因應用程序而異,但這些通用原則幾乎適用於所有 REST API,並在可能的情況下應予以遵循。
這樣做不僅允許客户端以一致的方式處理錯誤,還簡化了我們在實現 REST API 時編寫的代碼。