當(dāng)一個(gè) API 請求沒能成功的時(shí)候,客戶端最好能收到一個(gè)正確的 HTTP 錯誤狀態(tài),例如 409 或 500,這會是一個(gè)好的開始。不幸的是,盡管 400 Bad Request 可能就足夠讓我們知道錯誤出在哪里,但常見的情況是我們沒有充足的信息來理解或解決實(shí)際遇到的問題。
許多 API 會在響應(yīng)正文中為你提供更多細(xì)節(jié),但令人遺憾的是,每個(gè) API 都有自己的定制風(fēng)格,不同的 API,甚至在各個(gè)端點(diǎn)使用的報(bào)告樣式都不一樣。這就需要定制的邏輯或者人工干預(yù)才能理解報(bào)告的內(nèi)容。
但這種情況并非無可避免。先不用急著否定我。試著想象一個(gè)更好的世界,其中每個(gè) API 都以相同的標(biāo)準(zhǔn)格式返回錯誤信息。
我們可以用一致的標(biāo)識符來識別各種類型的錯誤,并能在任何地方輕松獲得清晰的描述和元數(shù)據(jù)。你的通用 HTTP 客戶端可以自動為任何錯誤提供詳盡細(xì)節(jié),你的客戶端錯誤處理機(jī)制則能輕松可靠地過濾出你所關(guān)心的特定錯誤,并且你能使用單組共享邏輯來處理多種 API 的常見錯誤。
IETF 提出的 RFC 7807 標(biāo)準(zhǔn)希望定義一個(gè) HTTP API 錯誤響應(yīng)的標(biāo)準(zhǔn)格式,從而實(shí)現(xiàn)上述目標(biāo)。它已經(jīng)開始在現(xiàn)實(shí)世界中得到應(yīng)用了。人們可以很容易地用它來支持現(xiàn)有的 API 和客戶端,對于構(gòu)建或使用 HTTP API 的人來說,這個(gè)標(biāo)準(zhǔn)值得關(guān)注。
標(biāo)準(zhǔn)錯誤格式為什么這么有用?
請不要這樣做
談這個(gè)問題前,讓我們先退一步思考問題。HTTP 的一個(gè)關(guān)鍵特性是標(biāo)準(zhǔn)的響應(yīng)狀態(tài)代碼,例如 200 或 404。正確使用這些代碼可確保客戶端自動理解響應(yīng)的總體狀態(tài),并根據(jù)對應(yīng)狀態(tài)采取適當(dāng)措施。
狀態(tài)代碼對錯誤處理來說特別有用。當(dāng)請求收到意外的 500 狀態(tài)時(shí),幾乎所有標(biāo)準(zhǔn)的 HTTP 客戶端都會自動為你拋出一個(gè)錯誤,并不需要自定義規(guī)則來解析和解釋各處的所有響應(yīng),從而確保意外錯誤能被可靠地報(bào)告,并可以在任何位置輕松處理。
這是很好的機(jī)制,但它也有很大的局限性。
實(shí)際上,一個(gè) HTTP 400 響應(yīng)可能表示以下任何一種情況:
- 你的請求格式錯誤,無法解析
- 你的請求意外為空,或缺少一些必需的參數(shù)
- 你的請求有效,但仍然模棱兩可,因此無法處理
- 你的請求有效,但由于服務(wù)器錯誤,服務(wù)器認(rèn)為請求無效
- 你的請求有效,但請求的是完全不可能的內(nèi)容
- 你的請求已啟動,但服務(wù)器拒絕了你提供的參數(shù)值
- 你的請求已啟動,但服務(wù)器拒絕了你提供的每個(gè)參數(shù)值
- 你的請求已啟動,但你的銀行拒絕了其中包含的銀行卡資料
- 你的請求完成了一項(xiàng)購買操作,但請求的其他部分在稍后階段被拒絕
這些全是錯誤,看起來都是由一個(gè)“壞”請求觸發(fā)的 400 錯誤,但它們的內(nèi)容卻大相徑庭。
狀態(tài)代碼可幫助我們區(qū)分錯誤和成功狀態(tài),但沒法區(qū)分得太細(xì)致。因此,HTTP 客戶端庫不能在拋出的錯誤中包含任何有用的細(xì)節(jié),每個(gè) API 客戶端都必須編寫自定義的處理機(jī)制來解析每個(gè)失敗的響應(yīng),并自行找出可能的原因和下一步應(yīng)該做的操作。
如果失敗的 HTTP 請求自動拋出的異常消息不僅僅是HTTP Error: 400 Bad Request,而是 Credit card number is not valid,這樣豈不更好?
只要錯誤有一套標(biāo)準(zhǔn)格式,上面提到的每個(gè)錯誤就都可以有自己的唯一標(biāo)識符,并包含標(biāo)準(zhǔn)化的說明內(nèi)容和指向更多細(xì)節(jié)的鏈接。好處是:
- 通用工具可以為你解析和解釋錯誤的詳細(xì)信息,而無需事先了解任何與 API 相關(guān)的信息。
- API 能更安全地發(fā)起錯誤響應(yīng),因?yàn)樗肋@些錯誤類型標(biāo)識符意味著即使解釋消息發(fā)生了更改,客戶端也會一直按同一種理解方式識別錯誤。
- 自定義 API 客戶端可以檢查錯誤類型以輕松處理特定情況,所有操作都以一種標(biāo)準(zhǔn)方式進(jìn)行,適用于你所使用的每個(gè) API,而無需從頭開始編寫 API 包裝程序,也用不著每次都和 API 文檔大戰(zhàn)三百回合。
提案的錯誤格式長什么樣?
為此,RFC7807 提出了一組用于返回錯誤的標(biāo)準(zhǔn)字段,以及兩種將其格式化為 JSON 或 XML 的內(nèi)容類型。
格式如下:
{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/transactions/abc"
}
對于 XML 的等效格式:
<?xml version="1.0" encoding="UTF-8"?>
<problem xmlns="urn:ietf:rfc:7807">
<type>https://example.com/probs/out-of-credit</type>
<title>You do not have enough credit.</title>
<detail>Your current balance is 30, but that costs 50.</detail>
<instance>/account/12345/transactions/abc</instance>
</problem>
這些 RFC 為此定義了兩種新的對應(yīng)內(nèi)容類型:Application/problem+json或application/problem+xml。返回錯誤的 HTTP 響應(yīng)應(yīng)在其Content-Type響應(yīng)標(biāo)頭中包含適當(dāng)?shù)膬?nèi)容類型,并且客戶端可以檢查該標(biāo)頭以確認(rèn)格式。
這個(gè)示例包括規(guī)范定義的一些標(biāo)準(zhǔn)化字段。完整列表是如下:
- type:標(biāo)識錯誤類型的 URI。在瀏覽器中加載這個(gè) URI 應(yīng)該轉(zhuǎn)向這個(gè)錯誤的文檔,但這不是嚴(yán)格要求的。此字段可用于識別錯誤類。理論上講,將來站點(diǎn)甚至可以為常見情況共享標(biāo)準(zhǔn)化的錯誤 URI,以使通用客戶端自動檢測到它們。
- title:錯誤的簡短可讀摘要。這是明確的指引,客戶端必須使用 type 作為識別 API 錯誤類型的主要方式。
- detail:較長的人類可讀解釋,帶有完整的錯誤詳細(xì)信息。
- status:錯誤使用的 HTTP 狀態(tài)代碼。它必須與實(shí)際狀態(tài)匹配,但可以包含在這里的 body 中以便參考。
- instance:標(biāo)識該特定故障實(shí)例的 URI。它可以作為發(fā)生的這個(gè)錯誤的 ID,和/或到特定故障更多詳細(xì)信息的鏈接,例如顯示失敗的信用卡交易細(xì)節(jié)的頁面。
所有這些字段都是可選的(不過type是強(qiáng)烈建議使用的)。內(nèi)容類型允許自由包含其他數(shù)據(jù),只要它們不與這些字段沖突即可,因此你也可以在此處添加自己的錯誤元數(shù)據(jù),并包含所需的其他任何數(shù)據(jù)。實(shí)例 URI 和類型 URI 都可以是絕對 URI,也可以是相對 URI。
這里的思想是:
- 通過返回帶有合適Content-Type標(biāo)頭的錯誤響應(yīng),API 能很容易地表明它們正在遵循這一標(biāo)準(zhǔn)。
- 這是一組簡單的字段,可以輕松添加到大多數(shù)現(xiàn)有的錯誤響應(yīng)頂部(如果還沒有添加的話)。
- 客戶端只需在請求中包含Accept: application/problem+json(和/或+xml)標(biāo)頭,即可輕松表明支持狀態(tài),從而在必要時(shí)進(jìn)行遷移。
- 客戶端邏輯可以輕松識別這些響應(yīng),并使用它們來顯著改善通用和按 API 區(qū)分的 HTTP 錯誤處理操作。
怎樣開始使用它呢?
目前,這是一個(gè)提案的標(biāo)準(zhǔn),因此它尚未普及,并且在理論上可能會發(fā)生變化。
不過它已經(jīng)用在很多地方,包括 5G 標(biāo)準(zhǔn)之類中,并且有了適用于大多數(shù)語言和框架的一些便捷工具,包括:
- ASP.NET的內(nèi)置支持
- Node.js 的通用庫和Express庫
- JAVA 的通用庫和Spring Web MVC庫
- Python 的通用庫和Django REST API庫
- Ruby 的通用、Rails和Sinatra庫
- php 的通用庫和Symfony庫
- Rust、Go、Scala、Haskell的庫...
也就是說,它已經(jīng)滲透到了大多數(shù)主流生態(tài)系統(tǒng)中站穩(wěn)了腳跟,并且即將更進(jìn)一步:讓更多 API 和客戶端開始使用,直到實(shí)現(xiàn)大規(guī)模的普及狀態(tài),即大多數(shù) API 的錯誤格式都遵循其標(biāo)準(zhǔn),使它成為所有場景的默認(rèn)值,讓我們大家都能從中受益。
我們該如何做到這一點(diǎn)呢?
如果你在構(gòu)建或維護(hù)一個(gè) API:
- 如果可以的話,請嘗試使用適當(dāng)?shù)腃ontent-Type響應(yīng)標(biāo)頭以RFC 7807格式返回錯誤。
- 如果你有了一種錯誤格式,并且需要維護(hù)該格式以保證兼容性,請檢查是否可以在格式頂部添加這些字段,并對其進(jìn)行擴(kuò)展以符合標(biāo)準(zhǔn)。
- 如果不能,請嘗試檢測傳入的Accept標(biāo)頭中的支持,并在可能的情況下使用它來將原有的錯誤格式切換為標(biāo)準(zhǔn)格式。
- 使用你的 API?框架(比如這個(gè)框架)記錄錯誤,表明它們將來會轉(zhuǎn)向標(biāo)準(zhǔn)錯誤格式。
如果你在使用一個(gè) API:
- 檢查這些內(nèi)容類型的錯誤響應(yīng),并用那里提供的數(shù)據(jù)改進(jìn)你的錯誤報(bào)告和處理工作。
- 考慮在請求中包含帶有這些內(nèi)容類型的Accept標(biāo)頭,以表明支持狀態(tài)并在可用時(shí)啟用標(biāo)準(zhǔn)錯誤。
- 向你使用的 API 提出抱怨,希望它們以這種標(biāo)準(zhǔn)格式返回錯誤,就像抱怨那些不會費(fèi)心返回正確狀態(tài)代碼的 API 一樣。
至于大家:
- 參與其中!這是 IETF 新的“HTTP API 的構(gòu)建塊”工作組旗下的規(guī)范。你可以加入郵件列表以閱讀并參與有關(guān)該規(guī)范和其他可行 API 標(biāo)準(zhǔn)規(guī)范規(guī)范的討論,包括速率限制和API棄用等。
- 向你的同事和開發(fā)者朋友做宣傳,并幫助大家簡化錯誤處理的工作。
原文鏈接:
https://httptoolkit.tech/blog/http-api-problem-details/