後端面試:如何用 RFC 9457 統一 HTTP API 錯誤?
面試官考察點
一個多租戶 API 的錯誤回應來自閘道、應用程式與非同步工作,客戶端難以穩定解析。請依 RFC 9457 設計統一錯誤契約,並說明狀態碼、媒體類型、錯誤類型 URI、批次驗證、重試、日誌脫敏與版本相容。
背景與約束
- 同一請求可能經過 CDN、API 閘道、業務服務與工作佇列。
- 客戶端需要區分可修正的輸入錯誤、權限問題、限流與暫時性故障。
- 錯誤詳情不能洩露堆疊、金鑰、租戶隔離資訊或內部主機名稱。
- 舊客戶端仍會解析既有欄位,不能突然把所有失敗改成新的業務狀態碼。
先分離 HTTP 語意與業務細節
HTTP 狀態碼表達請求在協定層的結果,Problem Details 負載解釋具體原因。400、401、403、404、409、429 與 5xx 仍按語意選擇,不能把所有失敗包成 200。錯誤類型用穩定 URI 識別可機器處理的類別,title 可供展示,detail 只能描述目前實例。
建立最小且可擴充的欄位契約
核心欄位包括 type、title、status、detail 與 instance。業務擴充欄位使用明確命名,例如 errors 表示欄位問題、retryAfter 表示等待建議。type 的文件頁應說明語意、適用狀態碼與可採取的動作;客戶端不要依賴 title 的自然語言。
讓閘道與應用程式共享錯誤邊界
閘道產生的逾時、驗證與限流錯誤也使用相同媒體類型,但不能偽造應用程式的業務 type。應用程式向上游保留可觀測的內部錯誤碼,對外只回傳允許公開的 Problem Details。跨服務傳遞時攜帶關聯 ID,不複製敏感的 detail。
回答前需要釐清的問題
- 客戶端依狀態碼、
type還是舊版code分支?這決定相容層與遷移順序。 - 是否需要單次回應傳回多個欄位驗證錯誤?這決定
errors結構與順序保證。 - 閘道能否讀取業務錯誤,還是只負責傳輸與產生基礎錯誤?這決定類型 URI 的所有權。
30 秒回答框架
「我會保留正確的 HTTP 狀態碼,再用 application/problem+json 回傳穩定的 type、title、status、detail 與可選的 instance。客戶端依 type 和狀態碼處理,不能依賴文案;閘道只產生它負責的錯誤類型。欄位驗證、重試提示、關聯 ID 和脫敏規則進入版本化契約,並用相容矩陣與真實鏈路驗證。」
分步驟深入解答
先定義錯誤類型註冊表:每個類型有 URI、公開欄位、允許狀態碼、客戶端動作與安全等級。請求驗證失敗回傳 400,欄位問題集中放入 errors;身份缺失與權限不足區分 401 與 403;並發版本衝突使用 409;限流回傳 429 並在回應標頭與擴充欄位提供等待建議;未知故障回傳 500 或 503,避免把內部例外類型暴露給呼叫方。
回應標頭的 Content-Type 必須與負載一致。錯誤體可以包含 instance 作為單次請求的追蹤引用,但不能把完整 URL、SQL、堆疊或租戶識別直接放進 detail。伺服器日誌保存內部原因、關聯 ID 與安全稽核欄位,客戶端只看到經過策略過濾的內容。
對批次驗證,errors 使用欄位路徑到問題清單的結構,並規定是否允許多個問題、欄位路徑語法與最大數量。客戶端遇到未知擴充欄位時應忽略;伺服器新增欄位只能向後相容,改變既有 type 語意則發布新 URI。非同步工作失敗透過工作資源狀態回傳 Problem Details,不把佇列內部例外直接同步給使用者。
高品質示範回答
「我會維護錯誤類型註冊表,讓閘道、同步服務與非同步工作都輸出 RFC 9457 相容的 application/problem+json。狀態碼表達協定語意,type 表達穩定類別,detail 只描述目前請求。欄位驗證使用受限的 errors 擴充;429 同時提供可解析的等待提示;500 和 503 使用通用公開類型,內部堆疊只進日誌。遷移時保留舊 code,透過契約測試、客戶端相容矩陣與脫敏稽核逐步切換。」
常見錯誤
- 使用 200 搭配業務失敗欄位,讓快取、監控與重試器誤判成功。
- 讓客戶端依
title或detail文案分支,翻譯或措辭變更就破壞相容。 - 每個服務任意定義
type,同一語意出現多個 URI,無法統一統計。 - 在
detail回傳堆疊、SQL、內部網域或完整租戶識別。 - 把閘道逾時偽裝成業務錯誤,客戶端因而錯誤重試或錯誤提示。
錯誤表現與修正
若客戶端無法判斷錯誤是否可重試,通常是狀態碼、類型和重試建議沒有形成一致契約。修正時先建立類型註冊表,再讓各層映射到有限的公開類型;對未知類型提供安全預設行為,並用日誌關聯 ID 追蹤內部原因。
生產化實作
在共享函式庫或邊緣轉接層集中序列化 Problem Details,保留服務邊界上的狀態碼選擇。透過 schema 驗證限制擴充欄位長度、陣列數量與 URI 格式;敏感欄位在序列化前過濾,而非只依賴閘道。對 429、503 與網路逾時分別定義指數退避、抖動和冪等條件,避免重試風暴。
驗證清單
契約測試覆蓋每個公開 type 的狀態碼、媒體類型、必要欄位和擴充欄位;整合測試穿過閘道、服務與佇列,斷言關聯 ID 與脫敏結果;相容測試用舊客戶端驗證未知欄位和保留 code;壓測觀察錯誤序列化延遲、日誌取樣與限流下的重試放大。
追問及應對
為什麼不能只定義一個業務錯誤碼?
單一錯誤碼無法表達 HTTP 快取、驗證、限流與重試語意。保留狀態碼能讓通用基礎設施正確工作,type 再承載穩定的業務類別,兩者職責不同。
type URI 必須可以存取嗎?
規範允許相對或絕對 URI;團隊應選擇穩定、可文件化的形式。若提供文件頁,應避免把頁面可用性作為客戶端處理錯誤的前提。
如何避免錯誤體本身造成資訊洩露?
建立公開欄位白名單、長度限制與敏感模式掃描;將內部例外映射為通用類型,並在日誌保留關聯 ID。安全測試要覆蓋跨租戶查詢、權限失敗與例外堆疊路徑。
評分標準
- 語意準確:能區分 HTTP 狀態碼與 Problem Details 欄位。
- 契約設計:提出穩定
type、擴充欄位與版本策略。 - 邊界意識:涵蓋閘道、非同步工作、脫敏與重試風暴。
- 實作可落地:有註冊表、序列化邊界、相容矩陣與測試。
- 風險控制:不洩露內部細節,也不把未知類型當成可重試。
合規檢查
確認狀態碼、類型欄位、脫敏策略與重試邊界在回答中保持一致。
面試作答要點
先說狀態碼仍表達 HTTP 語意,再說明 type 是機器穩定識別、detail 不是契約鍵;接著補上閘道邊界、欄位驗證、重試、脫敏與相容驗證。
一句話總結
統一錯誤的關鍵是讓狀態碼負責協定語意、type 負責穩定分類、擴充欄位負責可行動細節,並以安全和相容測試守住邊界。