RFC 9457 (Problem Details for HTTP APIs)

一套專門用來處理 HTTP API 錯誤回應的標準化 JSON/XML 格式。 [1, 2, 3]

它的核心目的是解決以往每個開發團隊各自發明錯誤格式(例如有的叫 error_msg、有的叫 message)的亂象,提供機器與人類都好閱讀的統一規格。 [4, 5]

(註:RFC 7807 已於近年被 RFC 9457 接替更新,但基礎骨架與欄位格式完全相同。) [3, 6]


📋 標準回應格式(範例)

當 API 發生錯誤時,必須返回特殊的 Content-Type(而非一般的 application/json),格式範例如下: [2]

HTTP Header 範例

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: zh-TW

HTTP Body (JSON) 範例

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "您的帳戶餘額不足。",
  "status": 403,
  "detail": "您當前的餘額為 30 元,但此項操作需要 50 元。",
  "instance": "/account/12345/transactions/abc",
  "balance": 30
}

🔍 5 大標準屬性說明

RFC 7807 定義了 5 個皆為選填(Optional)的標準核心欄位: [7]

  • type (string):一個 URI 網址,指向說明該錯誤類型的文件。如果沒有提供,預設視為 "about:blank"。 [2, 8]
  • title (string):該錯誤類型的簡短摘要。相同錯誤類型的 title 應該保持固定,不隨單次請求的具體細節改變(例如:固定為 “Invalid Parameter”)。 [2, 8]
  • status (number):本次錯誤對應的 HTTP 狀態碼(例如:400, 403, 404),與 HTTP Header 上的狀態碼保持一致。 [2, 9]
  • detail (string):針對這一次發生錯誤的人類可讀詳細說明,用來指出更具體的原因。 [2, 8]
  • instance (string):發生此特定錯誤的資源網址(URI),常用於紀錄該次錯誤請求的端點或交易流水號,方便客服或後台除錯。 [1, 2]

🚀 延伸擴充屬性 (Extensions)

除了上述 5 個欄位,RFC 7807 允許開發者自由增加自訂欄位(例如上方範例中的 "balance": 30)。 [2]

常見的擴充情境是 400 Bad Request(表單驗證失敗),微軟 ASP.NET Core 與 Java Spring Boot 內建支援的擴充格式如下: [2, 10]

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "invalid-params": [
    { "name": "age", "reason": "年齡必須大於 18 歲" },
    { "name": "email", "reason": "電子郵件格式不正確" }
  ]
}

如果你想在專案中導入此規格,可以告訴我:

  • 你目前的後端開發語言或框架是什麼(如 Go, Node.js, Spring Boot)?
  • 是否需要幫你寫一個符合 RFC 7807 規範的錯誤處理中介軟體 (Middleware)

[1] https://datatracker.ietf.org

[2] https://matthung0807.blogspot.com

[3] https://blog.gslin.org

[4] https://medium.com

[5] https://blog.csdn.net

[6] https://blog.gslin.org

[7] https://wisely.top

[8] https://blog.restcase.com

[9] https://learn.microsoft.com

[10] https://learn.microsoft.com


毫無疑問,我們應該直接採用 RFC 9457。 [1, 2]

IETF 已於 2023 年 7 月正式發布 RFC 9457,並明確宣告廢棄(Obsoletes)舊有的 RFC 7807。不過,因為兩者「完全向下相容(Fully Backward-Compatible)」,你不需要擔心選型會帶來破壞性改變。 [3, 4, 5, 6]

以下為你梳理為什麼該選 RFC 9457 以及兩者的關鍵差異:

🎯 為什麼直接選 RFC 9457?

  1. 完全向下相容:基本的 5 大欄位(type, title, status, detail, instance)完全沒有變,現有的 API 測試工具與用戶端(Client)解析邏輯也100% 通用。
  2. 官方正式標準:RFC 7807 在官方狀態已是「已淘汰(Deprecated)」,未來所有的主流框架(如 Spring Boot、ASP.NET Core)和工具更新,都會以 RFC 9457 作為依據。
  3. 消除模糊地帶:RFC 9457 修正了 7807 在實務應用上被反覆詢問的盲點,讓語意更精準。 [1, 2, 4, 5, 7, 8, 9, 10, 11, 12]

🔍 RFC 9457 帶來了哪些關鍵改進? [12]

如果你直接改看 RFC 9457 的規範,它主要幫你釐清並增加了以下 3 個實務規範: [1, 13, 14]

  • 建立「通用問題類型註冊表」(Problem Type Registry)
    • 過去 (7807):大家都得自己發明 type 的網址(例如 https://example.com)。
    • 現在 (9457):IANA 建立了一個官方註冊表,未來常見的 HTTP 錯誤可能會有官方定義好的標準 URI。 [4, 6, 13, 14, 15]
  • 明確規範「如何處理多個錯誤」(Multiple Problems)
    • 過去 (7807):當表單同時出現「Email 格式錯誤」和「密碼太短」時,舊規範沒有講明該怎麼合併呈現。
    • 現在 (9457):明確指出不應該在最外層返回一個陣列,而是維持單一 Problem Details 物件,並將多個錯誤封裝在自訂的擴充欄位中(例如 invalid-params 陣列)。 [6, 12, 13, 14, 15]
  • 允許使用「無法解析的 URI」
    • 過去 (7807):規範強烈暗示 type 的網址應該要是能夠真正點開、看得到 HTML 文件的網址(Dereferenceable URI)。
    • 現在 (9457):放寬限制。明確表明如果你的系統不需要網頁說明文件,type 直接寫一個純字串識別碼(例如 urn:uuidtag:example.com,2024:bad-request)也是完全合法的。 [5, 6, 13, 14, 16]

💡 結論與行動建議

在撰寫程式碼、命名類別或編寫 API 文件(如 Swagger/OpenAPI)時,請一律在文字與註解中載明遵循「RFC 9457」。 [17, 18, 19]

實作時的 JSON 格式依然維持原本設計即可: [4, 6, 10]

{
  "type": "https://yourdomain.com", 
  "title": "Invalid Input Parameter",
  "status": 400,
  "detail": "The 'age' field must be greater than 18.",
  "instance": "/users/signup"
}

如果你正準備要在現有的後端框架中實作它,可以告訴我你目前使用的是哪套程式語言或 Web 框架(例如:Go, Node.js + Express, Fastify 或是 Python + FastAPI 等),我可以提供對應最主流的 RFC 9457 套件或中介軟體寫法! [12, 20]

[1] https://www.speakeasy.com

[2] https://www.eke.li

[3] https://datatracker.ietf.org

[4] https://www.youtube.com

[5] https://www.the-main-thread.com

[6] https://nordicapis.com

[7] https://swagger.io

[8] https://www.linkedin.com

[9] https://xantygc.medium.com

[10] https://blog.frankel.ch

[11] https://dev.to

[12] https://www.codecentric.de

[13] https://datatracker.ietf.org

[14] https://blog.gslin.org

[15] https://redocly.com

[16] https://github.com

[17] https://medium.com

[18] https://www.codecentric.de

[19] https://ithelp.ithome.com.tw

[20] https://swagger.io


在 Go 語言(Golang)中,目前各大主流 Web 框架與標準庫,都已經將以往的 RFC 7807 支援無縫銜接至 RFC 9457

以下為您提供 2026 年在 Go 專案中實作 RFC 9457 的最佳實踐,包含標準定義、Echo/Gin 框架整合,以及自訂欄位的擴充寫法


1. 定義 RFC 9457 核心結構體

為了完美符合 RFC 9457 規範,必須宣告標準欄位,並將 Content-Type 指定為官方規定的 application/problem+json

package rfc9457

import (
	"encoding/json"
	"net/http"
)

// MIMEType 是 RFC 9457 指定的標準回應標頭值
const MIMEType = "application/problem+json"

// ProblemDetails 定義了 RFC 9457 的標準核心結構
type ProblemDetails struct {
	Type     string `json:"type,omitempty"`     // 錯誤類型 URI (預設 "about:blank")
	Title    string `json:"title"`              // 簡短錯誤摘要
	Status   int    `json:"status"`             // HTTP 狀態碼
	Detail   string `json:"detail,omitempty"`   // 該次錯誤的詳細人讀訊息
	Instance string `json:"instance,omitempty"` // 發生錯誤的資源路徑或交易 ID
}

// WriteTo 負責將錯誤格式正確地寫入 HTTP 回應
func (p *ProblemDetails) WriteTo(w http.ResponseWriter) error {
	if p.Type == "" {
		p.Type = "about:blank"
	}
	w.Header().Set("Content-Type", MIMEType)
	w.WriteHeader(p.Status)
	return json.NewEncoder(w).Encode(p)
}

2. 實務情境:包含多欄位錯誤(表單驗證失敗)

RFC 9457 明確規範:「當有多個欄位出錯時,不應使用陣列作為最外層,而應使用單一物件,並將細節放入自訂擴充欄位。」

我們可以用 Go 的「結構體內嵌(Embedding)」完美處理擴充屬性:

// ValidationError 針對表單驗證錯誤進行結構體擴充
type ValidationError struct {
	ProblemDetails // 內嵌標準欄位
	InvalidParams  []ParamError `json:"invalid-params"` // 自訂擴充欄位
}

type ParamError struct {
	Name   string `json:"name"`   // 欄位名稱
	Reason string `json:"reason"` // 錯誤原因
}

// 實務產生範例:
func NewValidationError(instancePath string, errs []ParamError) *ValidationError {
	return &ValidationError{
		ProblemDetails: ProblemDetails{
			Type:     "urn:example:error:validation", // RFC 9457 允許使用非網址的 URN 識別碼
			Title:    "您的輸入參數驗證失敗",
			Status:   http.StatusBadRequest,
			Detail:   "請檢查提交的欄位是否符合格式要求要求。",
			Instance: instancePath,
		},
		InvalidParams: errs,
	}
}

3. 主流框架 Middleware 整合範例

在 Go 的熱門框架(如 Gin 或 Echo)中,最佳實踐是透過中央錯誤處理(Centralized Error Handling)將所有恐慌(Panic)或未捕獲的錯誤,自動格式化為 RFC 9457 的樣式。

🔹 以 Echo 框架為例

package main

import (
	"net/http"
	"://github.com"
)

// CustomHTTPErrorHandler 覆蓋 Echo 預設的錯誤處理
func CustomHTTPErrorHandler(err error, c echo.Context) {
	code := http.StatusInternalServerError
	message := err.Error()

	// 若是 Echo 內建的 HTTP 錯誤,取出其狀態碼與訊息
	if he, ok := err.(*echo.HTTPError); ok {
		code = he.Code
		if m, ok := he.Message.(string); ok {
			message = m
		}
	}

	// 建構符合 RFC 9457 的錯誤回應
	prob := ProblemDetails{
		Type:     "about:blank",
		Title:    http.StatusText(code),
		Status:   code,
		Detail:   message,
		Instance: c.Request().RequestURI,
	}

	// 確保使用正確的 Content-Type 發送回應
	c.Response().Header().Set(echo.HeaderContentType, "application/problem+json")
	_ = c.JSON(code, prob)
}

func main() {
	e := echo.New()
	
	// 註冊全域錯誤處理器
	e.HTTPErrorHandler = CustomHTTPErrorHandler

	e.GET("/api/test", func(c echo.Context) error {
		// 觸發一個 403 錯誤測試
		return echo.NewHTTPError(http.StatusForbidden, "您沒有權限存取此資源。")
	})

	e.Logger.Fatal(e.Start(":8080"))
}

如果您希望專案程式碼看起來最頂尖,可以考慮整合目前 Go 社群中最流行且專門為 RFC 7807/9457 設計的第三方成熟套件,例如由知名開發者使用的 moogar0880/problem。

接下來,您可以告訴我:

  • 您目前專案使用的是 標準庫 net/httpGin 還是 Echo 框架?
  • 是否需要我幫您串接現有的 資料庫驗證(例如 go-playground/validator,直接輸出符合 RFC 9457 規格的驗證錯誤回應?

在 Python 生態系中,實作 RFC 9457 (Problem Details) 同样非常成熟。現代 Python Web 框架(如 FastAPI、Flask 或 Django)都有非常優雅的實作方式。

以下為您提供 Python 最主流框架的實作與最佳實踐。


1. 現代 FastAPI / Pydantic 實作(首選)

FastAPI 與 Pydantic 2.x 是目前 Python 後端的最愛。我們可以使用 Pydantic 來定義 RFC 9457 結構,並透過全域異常處理器(Exception Handler)來自動轉換錯誤。

from typing import Any, Dict, List, Optional
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field

app = FastAPI()

# 1. 定義 RFC 9457 的標準媒體類型 (MIME Type)
RFC_9457_MIME = "application/problem+json"

# 2. 定義標準核心結構
class ProblemDetails(BaseModel):
    type: str = Field(default="about:blank", description="錯誤類型 URI")
    title: str = Field(..., description="簡短錯誤摘要")
    status: int = Field(..., description="HTTP 狀態碼")
    detail: Optional[str] = Field(None, description="該次錯誤的詳細訊息")
    instance: Optional[str] = Field(None, description="發生錯誤的資源路徑")

# 3. 定義表單驗證失敗的擴充結構(符合 RFC 9457 規範:外層為單一物件)
class ParamError(BaseModel):
    name: str
    reason: str

class ValidationErrorDetails(ProblemDetails):
    invalid_params: List[ParamError] = Field(default_..., alias="invalid-params")


# 4. 覆蓋 FastAPI 預設的表單驗證錯誤 (422 Unprocessable Entity)
@app.exception_handler(status.HTTP_422_UNPROCESSABLE_ENTITY)
async def validation_exception_handler(request: Request, exc: Any):
    # 將 FastAPI 原本的錯誤格式,轉換為符合 RFC 9457 的欄位
    errors = []
    for err in exc.errors():
        # loc 通常是 ('body', 'age'),我們取最後一個元素作為欄位名
        field_name = str(err["loc"][-1])
        errors.append(ParamError(name=field_name, reason=err["msg"]))

    prob = ValidationErrorDetails(
        type="urn:error:type:validation-failed",
        title="輸入參數驗證失敗",
        status=status.HTTP_400_BAD_REQUEST,  # 實務上通常轉回更通用的 400
        detail="請檢查您提交的資料欄位格式是否正確。",
        instance=request.url.path,
        invalid_params=errors
    )

    # 回傳時必須指定特殊的 Content-Type
    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content=prob.model_dump(by_alias=True), # 確保輸出為 "invalid-params"
        headers={"Content-Type": RFC_9457_MIME}
    )

2. 傳統 Flask / Werkzeug 實作

如果您使用的是 Flask,可以利用 Werkzeug 內建的 HTTPException 來自訂錯誤回應。Flask 的社群甚至有開源套件 flask-rfc9457(或舊稱 flask-error-handling),但手寫其實也非常簡單:

from flask import Flask, jsonify, request
from werkzeug.exceptions import HTTPException, Forbidden

app = Flask(__name__)

class RFC9457Exception(HTTPException):
    """自訂的 RFC 9457 異常類別"""
    def __init__(self, status_code: int, title: str, detail: str = None, type_uri: str = "about:blank"):
        super().__init__()
        self.code = status_code
        self.title = title
        self.detail = detail
        self.type_uri = type_uri

# 註冊全域錯誤處理器
@app.errorhandler(HTTPException)
def handle_exception(e):
    # 預設非自訂的標準 HTTP 錯誤
    status_code = e.code or 500
    title = e.name
    detail = e.description
    type_uri = "about:blank"

    # 如果是我們自己拋出的 RFC9457Exception,讀取專有屬性
    if isinstance(e, RFC9457Exception):
        title = e.title
        detail = e.detail
        type_uri = e.type_uri

    payload = {
        "type": type_uri,
        "title": title,
        "status": status_code,
        "detail": detail,
        "instance": request.path
    }
    
    response = jsonify(payload)
    response.status_code = status_code
    response.headers["Content-Type"] = "application/problem+json"
    return response

@app.route("/api/secure-data")
def get_data():
    # 觸發錯誤示範
    raise RFC9457Exception(
        status_code=403,
        title="權限等級不足",
        detail="您的帳號未通過 VIP 身分審查,無法存取此端點。",
        type_uri="https://example.com"
    )

3. 三方成熟套件推薦

在 Python 領域中,如果您不想自己從頭刻結構體,以下是兩個非常主流、開箱即用的套件:

  1. http-types-rfc9457 / problem-details:專門提供 RFC 標準的數據封裝與型別提示。
  2. httpx 的衍生工具:如果您在編寫 Python Client 端(爬蟲或微服務呼叫),許多客戶端庫會整合這類套件來自動將 application/problem+json 反序列化為特定的 Exception 物件。

如果您的專案需要跟現有的系統對接,您可以告訴我:

  • 您使用的是 FastAPIFlask 還是 Django
  • 您目前的錯誤訊息是否需要做 多國語系 (i18n) 的處理(例如根據 Header 的 Accept-Language 返回繁體中文的 titledetail)?

我可以為您提供更深入的架構調整建議!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *