Documentation
¶
Overview ¶
client.go 提供 HTTP 客户端侧的 Request ID 透传能力。
使用方式(推荐):
client := &http.Client{
Transport: httpx.NewTracingRoundTripper(http.DefaultTransport),
}
// 业务代码:把带有 request_id 的 ctx 传给请求,自动加到 Header
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, _ := client.Do(req)
Package httpx —— HTTP 框架统一的 panic / error 恢复中间件。
中间件职责:
- 捕获 handler 中抛出的 panic,统一封装成 500 ApiException
- 捕获 handler 通过各框架原生机制返回的 error(如 c.Error / chain 错误)
- 所有错误响应都通过 Write*Error 走统一协议,自动携带 request_id
中间件顺序建议(以 Gin 为例):
r := gin.New() r.Use(tracemw.Gin(trace.DefaultConfig())) // 1) 先注入 request_id r.Use(httpx.GinMiddleware()) // 2) 再做 panic 恢复 r.Use(loggingMiddleware) // 3) 业务日志
Package httpx 内的语言协商辅助:把 HTTP Accept-Language 头归一化为 exception 包支持的内部语言标签(exception.LangZH / exception.LangEN)。
解析策略:
- 按 "," 拆分出多个语言项;
- 解析每一项的 "q=" 权重(缺省视为 1.0);
- 按权重降序排序,取第一条能够识别的语言;
- 均不能识别时退回默认语言(LangZH)。
只识别主语言标签的前缀(例如 "en-US" 归一化为 "en"),避免维护一张冗长的 语言子标签表;对于未知语言统一回退中文,保证行为可预期。
Package httpx —— 各 HTTP 框架 404/405 兜底处理器。
背景:
Gin / net/http / go-restful 等框架对于未注册的路由,会直接返回 纯文本 "404 page not found",不会经过业务中间件链。这导致: - 响应体不符合统一 Response 协议(缺 code/request_id/server_time) - 客户端需要额外写一套 404 解析逻辑 - 排障时缺失 request_id,难以串联日志 本文件统一提供兜底 Handler,将 404/405 也纳入统一响应协议。
各框架接入方式(建议放在所有路由注册之后):
// Gin —— 推荐使用一键装配,避免漏开 HandleMethodNotAllowed 开关
httpx.UseGinNotFound(r)
// Gin —— 手动装配(必须显式打开 HandleMethodNotAllowed,否则 405 会退化为 404)
r.HandleMethodNotAllowed = true
r.NoRoute(httpx.GinNotFoundHandler())
r.NoMethod(httpx.GinMethodNotAllowedHandler())
// Fiber v3 —— 推荐在 fiber.New 时通过配置注入全局 ErrorHandler
// 它能同时接管 Fiber 自己抛出的 404/405 错误(这些错误不走业务中间件链,仅走 ErrorHandler)
app := fiber.New(fiber.Config{
ErrorHandler: httpx.FiberErrorHandler(),
})
// Fiber v3 —— 兼容方式(作为最后一个 Use,仅能拦截路由未命中的 404,
// 无法拦截 Fiber 内部的 405/501 等早期错误)
app.Use(httpx.FiberNotFoundHandler())
// go-restful(Container 级别)
// ⚠️ 必须使用 restful.NewContainer(),不能用 restful.DefaultContainer!
// DefaultContainer 内部使用 http.DefaultServeMux,完全未注册的路径会被
// DefaultServeMux 直接返回 "404 page not found",根本不进 go-restful 的
// dispatch,ServiceErrorHandler 永远拦截不到。
// NewContainer() 会把 "/" 注册为兜底路由,所有请求都先进 dispatch,
// ServiceErrorHandler 才能正确拦截 404。
container := restful.NewContainer()
container.ServiceErrorHandler(httpx.RestfulServiceErrorHandler())
// 若实在必须用 DefaultContainer,可在最外层套 HTTPWithNotFound 兜底:
// http.ListenAndServe(":8080", httpx.HTTPWithNotFound(restful.DefaultContainer))
// net/http(Go 1.22+)
mux := http.NewServeMux()
// ... 注册业务路由 ...
// HTTPWithNotFound 会同时拦截下游的 404 和 405碰撦,改成统一协议输出
http.ListenAndServe(":8080", httpx.HTTPWithNotFound(mux))
关于 gRPC:
gRPC 调用不存在的方法时,框架会直接返回 codes.Unimplemented, **服务端拦截器链完全不会被触发**,这是 gRPC 协议层决定的, 无法在服务端通过中间件兜底。建议在客户端拦截器里统一处理, 或在网关层(如 grpc-gateway)统一包装。详见 README。
Package httpx 提供 HTTP 场景下的统一响应协议与响应写入能力。
统一响应协议(所有成功 / 失败响应共用同一结构):
{
"code": 0, // 0 表示成功;非 0 为业务/HTTP 错误码
"message": "xxx", // 错误信息(成功时可省略)
"data": {...}, // 业务数据(失败时可省略)
"request_id": "Xk2_...nano...", // 本次请求的追踪 ID
"server_time": 1714982400 // 服务端生成响应时的 Unix 秒时间戳
}
设计要点:
- 所有框架(Gin / Fiber / go-restful / net-http)提供风格一致的 WriteJSON / WriteError 入口
- request_id 自动从 ctx 中读取(需先挂载 logger/trace 中间件)
- 错误路径与成功路径共用同一结构,客户端只需一套解析逻辑
- 即使 trace 中间件未挂载,依然可正常工作(request_id 字段为空)
Package httpx 的 sanitizer.go:错误响应出口的全局脱敏钩子。
背景:ApiException.Message 可能携带后端引擎(docker daemon / containerd / registry 等)的原始错误文本,其中可能包含 socket 路径、主机 IP、文件系统 路径等基础设施信息。服务方通常希望"响应只给方向,全文留在日志"。
本钩子提供统一收口:所有框架(Gin / Fiber / go-restful / net-http)的 Write*Error 系列入口在把错误序列化为统一 Response 之前,都会先经过该钩子。
典型用法(服务启动时注册一次):
httpx.SetErrorSanitizer(func(ctx context.Context, err *exception.ApiException, requestID string) *exception.ApiException {
if err.GetHttpCode() >= 500 {
clone := *err
clone.Message = "服务内部错误,请稍后重试"
return &clone
}
return err
})
设计约束:
- 钩子不得修改入参 err(需要改动时请克隆后返回),避免污染调用方 / 日志记录;
- 钩子应快速返回(位于错误响应热路径);
- 钩子返回 nil 视为"不修改",按原 err 输出;
- 钩子 panic 不阻断错误响应:恢复后按原 err 输出。
Index ¶
- func FiberErrorHandler() fiber.ErrorHandler
- func FiberMiddleware() fiber.Handler
- func FiberNotFoundHandler() fiber.Handler
- func FiberWriteError(c fiber.Ctx, err *exception.ApiException) error
- func FiberWriteErrorFromError(c fiber.Ctx, err error) error
- func FiberWriteJSON(c fiber.Ctx, data interface{}) error
- func FiberWriteJSONStatus(c fiber.Ctx, status int, data interface{}) error
- func FromError(err error) *exception.ApiException
- func GinMethodNotAllowedHandler() gin.HandlerFunc
- func GinMiddleware(reporter ...PanicReporter) gin.HandlerFunc
- func GinNotFoundHandler() gin.HandlerFunc
- func GinWriteError(c *gin.Context, err *exception.ApiException)
- func GinWriteErrorFromError(c *gin.Context, err error)
- func GinWriteJSON(c *gin.Context, data interface{})
- func GinWriteJSONStatus(c *gin.Context, status int, data interface{})
- func HTTPMethodNotAllowedHandler() http.Handler
- func HTTPMiddleware() func(http.Handler) http.Handler
- func HTTPNotFoundHandler() http.Handler
- func HTTPWithNotFound(next http.Handler) http.Handler
- func RestfulFilter() restful.FilterFunction
- func RestfulServiceErrorHandler() restful.ServiceErrorHandleFunction
- func RestfulWriteError(req *restful.Request, resp *restful.Response, err *exception.ApiException)
- func RestfulWriteErrorFromError(req *restful.Request, resp *restful.Response, err error)
- func RestfulWriteJSON(req *restful.Request, resp *restful.Response, data interface{})
- func RestfulWriteJSONStatus(req *restful.Request, resp *restful.Response, status int, data interface{})
- func SetErrorSanitizer(fn ErrorSanitizer)
- func UseGinNotFound(engine *gin.Engine)
- func WriteError(w http.ResponseWriter, r *http.Request, err *exception.ApiException)
- func WriteErrorFromError(w http.ResponseWriter, r *http.Request, err error)
- func WriteJSON(w http.ResponseWriter, r *http.Request, data interface{})
- func WriteJSONStatus(w http.ResponseWriter, r *http.Request, status int, data interface{})
- type ErrorSanitizer
- type PanicReporter
- type Response
- type TracingRoundTripper
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FiberErrorHandler ¶
func FiberErrorHandler() fiber.ErrorHandler
FiberErrorHandler 返回一个 Fiber v3 的全局错误处理器,用于注入 fiber.Config.ErrorHandler。
背景:Fiber 内部在路由匹配阶段就可能抛出 ErrNotFound(404)/ ErrMethodNotAllowed(405)/ErrRequestTimeout 等错误,这些错误**不会进入**业务 中间件链(即 FiberMiddleware 也接不到),仅由全局 app.ErrorHandler 处理。 如果不替换默认的 DefaultErrorHandler,就会直接输出纯文本状态码,破坏统一协议。
使用:
app := fiber.New(fiber.Config{
ErrorHandler: httpx.FiberErrorHandler(),
})
app.Use(tracemw.Fiber(nil))
app.Use(httpx.FiberMiddleware())
// ... 注册业务路由 ...
func FiberMiddleware ¶
FiberMiddleware 统一处理 fiber v3 中的 panic 与 handler 返回错误
使用方式:
app := fiber.New() app.Use(httpx.FiberMiddleware())
func FiberNotFoundHandler ¶
FiberNotFoundHandler 返回 Fiber v3 的 404 兜底 Handler
使用(放在所有路由注册之后,作为最后一个 Use):
app.Use(httpx.FiberNotFoundHandler())
⚠️ 这种方式只能接管“路由完全未命中”的场景,无法拦截 Fiber 自己抛出的 ErrMethodNotAllowed(405)或 StatusNotImplemented(501)等前置错误—— 这些错误是由 Fiber 在路由分发阶段直接交给全局 app.ErrorHandler 的, 根本不会进入业务中间件链。推荐配合 FiberErrorHandler() 一起使用。
func FiberWriteError ¶
func FiberWriteError(c fiber.Ctx, err *exception.ApiException) error
FiberWriteError 错误响应(Fiber v3)
func FiberWriteErrorFromError ¶
FiberWriteErrorFromError 兼容任意 error
func FiberWriteJSON ¶
FiberWriteJSON 成功响应(Fiber v3)
使用示例:
app.Get("/ping", func(c fiber.Ctx) error {
return httpx.FiberWriteJSON(c, fiber.Map{"msg": "pong"})
})
func FiberWriteJSONStatus ¶
FiberWriteJSONStatus 自定义状态码的成功响应
func FromError ¶
func FromError(err error) *exception.ApiException
FromError 将任意 error 转换为 *ApiException 非 ApiException 时统一视为内部错误;对标准库可识别的错误(如请求体超限)映射为对应业务码。
func GinMethodNotAllowedHandler ¶
func GinMethodNotAllowedHandler() gin.HandlerFunc
GinMethodNotAllowedHandler 返回 Gin 的 405 兜底 Handler
⚠️ 必须搭配 engine.HandleMethodNotAllowed = true 使用!
Gin 默认 HandleMethodNotAllowed = false,此时即便路径已注册但方法不匹配, 也会直接走 NoRoute(即 404),不会进入 NoMethod(405)分支。 如果只注册本 Handler 而未打开开关,405 场景将全部被 404 吃掉。
手动装配示例:
r := gin.New() r.HandleMethodNotAllowed = true // 👈 关键开关,不要漏 r.NoMethod(httpx.GinMethodNotAllowedHandler())
推荐直接用一键装配:httpx.UseGinNotFound(r)
func GinMiddleware ¶
func GinMiddleware(reporter ...PanicReporter) gin.HandlerFunc
GinMiddleware 统一处理 gin 中的 panic 和错误输出
使用方式:
r := gin.New() r.Use(httpx.GinMiddleware())
handler 中如果返回错误,可通过 c.Error(err) 上报,或直接 panic 交给中间件处理。
可选参数 reporter:panic 恢复时回调(用于记日志);未传或传 nil 时静默不记。 为向后兼容采用可变参数,仅取第一个非 nil 的 reporter。
func GinNotFoundHandler ¶
func GinNotFoundHandler() gin.HandlerFunc
GinNotFoundHandler 返回 Gin 的 404 兜底 Handler
使用:
r.NoRoute(httpx.GinNotFoundHandler())
响应形如:
{"code":404,"message":"资源未找到: route not found: GET /xxx",
"request_id":"...","server_time":1714982400}
func GinWriteError ¶
func GinWriteError(c *gin.Context, err *exception.ApiException)
GinWriteError 错误响应(Gin)
func GinWriteErrorFromError ¶
GinWriteErrorFromError 兼容任意 error
func GinWriteJSON ¶
GinWriteJSON 成功响应(Gin)
使用示例:
r.GET("/ping", func(c *gin.Context) {
httpx.GinWriteJSON(c, gin.H{"msg": "pong"})
})
func GinWriteJSONStatus ¶
GinWriteJSONStatus 自定义状态码的成功响应
func HTTPMethodNotAllowedHandler ¶
HTTPMethodNotAllowedHandler 返回 net/http 的 405 兜底 Handler,仅供用户手动组装时使用。 通常你不需要直接调用它——直接用 HTTPWithNotFound 包裹 ServeMux 即可自动覆盖 404/405。
func HTTPMiddleware ¶
HTTPMiddleware 返回 net/http 的 panic 恢复中间件
使用示例:
handler := httpx.HTTPMiddleware()(myHandler)
http.ListenAndServe(":8080", handler)
func HTTPNotFoundHandler ¶
HTTPNotFoundHandler 返回 net/http 的 404 兜底 Handler
通常配合自定义 ServeMux 使用,或作为 http.DefaultServeMux 的替代。
func HTTPWithNotFound ¶
HTTPWithNotFound 包装已存在的 http.Handler(通常是 *http.ServeMux), 同时拦截下游写出的 **404** 与 **405** 响应,改由本包统一兜底输出。
背景:Go 1.22+ 的 http.ServeMux 会区分 404(路径未注册)和 405(路径注册了但方法不匹配), 两种错误都会直接输出“NotFound / Method Not Allowed”纯文本,不符合统一协议。
原理:对下游 ResponseWriter 做一层拦截,当 handler 调用 WriteHeader(404) 或 WriteHeader(405) 时不转发到底层,由外层按统一协议重新输出。405 时会保留下游已写入的 `Allow` 响应头。
使用:
mux := http.NewServeMux()
mux.HandleFunc("GET /hello", helloHandler)
http.ListenAndServe(":8080", httpx.HTTPWithNotFound(mux))
func RestfulFilter ¶
func RestfulFilter() restful.FilterFunction
RestfulFilter 统一处理 go-restful 中的 panic
使用方式:
wsContainer := restful.NewContainer() wsContainer.Filter(httpx.RestfulFilter())
func RestfulServiceErrorHandler ¶
func RestfulServiceErrorHandler() restful.ServiceErrorHandleFunction
RestfulServiceErrorHandler 返回 go-restful Container 级错误处理器
使用:
container := restful.NewContainer() // ⚠️ 必须用 NewContainer(),见下方说明 container.ServiceErrorHandler(httpx.RestfulServiceErrorHandler())
适用场景:路由匹配失败(404)、Method 不匹配(405)、Accept 不支持(406)等 由 go-restful 框架自身产生的错误,统一走本包响应协议。
⚠️ 关于 restful.DefaultContainer 的 404 拦截问题:
restful.DefaultContainer 内部使用 http.DefaultServeMux。
当请求路径完全未注册时,http.DefaultServeMux 会直接返回 "404 page not found",
根本不会进入 go-restful 的 dispatch 流程,ServiceErrorHandler 永远拦截不到。
(405 能被拦截是因为路径前缀已注册,请求能进入 dispatch,SelectRoute 失败后才触发)
解决方案(二选一):
1. 【推荐】改用 restful.NewContainer(),它会把 "/" 注册为兜底路由,
所有请求都先进 dispatch,ServiceErrorHandler 才能正确拦截 404。
2. 若必须用 DefaultContainer,在最外层套 HTTPWithNotFound 兜底:
http.ListenAndServe(":8080", httpx.HTTPWithNotFound(restful.DefaultContainer))
func RestfulWriteError ¶
RestfulWriteError 错误响应(go-restful v3)
func RestfulWriteErrorFromError ¶
RestfulWriteErrorFromError 兼容任意 error
func RestfulWriteJSON ¶
RestfulWriteJSON 成功响应(go-restful v3)
使用示例:
ws.Route(ws.GET("/ping").To(func(req *restful.Request, resp *restful.Response) {
httpx.RestfulWriteJSON(req, resp, map[string]any{"msg": "pong"})
}))
func RestfulWriteJSONStatus ¶
func RestfulWriteJSONStatus(req *restful.Request, resp *restful.Response, status int, data interface{})
RestfulWriteJSONStatus 自定义状态码的成功响应
func SetErrorSanitizer ¶ added in v2.2.32
func SetErrorSanitizer(fn ErrorSanitizer)
SetErrorSanitizer 注册全局错误脱敏钩子。
通常在服务启动阶段调用一次;重复调用以后注册者为准;传 nil 表示清除钩子。
func UseGinNotFound ¶
UseGinNotFound 一键装配 Gin 的 404/405 兜底能力。
内部会完成三件事,消除易错配置:
- 打开 engine.HandleMethodNotAllowed = true(Gin 默认为 false, 不开启时 405 会退化为 404)
- 注册 NoRoute Handler —— 路径未命中 → 404
- 注册 NoMethod Handler —— 路径命中但方法不匹配 → 405
使用(建议放在所有路由注册之后,也可在之前,Gin 内部按请求触发):
r := gin.New() r.Use(trace.GinMiddleware(nil)) // ... 注册业务路由 ... httpx.UseGinNotFound(r)
func WriteError ¶
func WriteError(w http.ResponseWriter, r *http.Request, err *exception.ApiException)
WriteError 写出一个 ApiException 错误响应(net/http) 若 err 为 nil,视为 204 No Content
func WriteErrorFromError ¶
func WriteErrorFromError(w http.ResponseWriter, r *http.Request, err error)
WriteErrorFromError 兼容任意 error
func WriteJSON ¶
func WriteJSON(w http.ResponseWriter, r *http.Request, data interface{})
WriteJSON 成功响应(net/http)
使用示例:
func handler(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, r, map[string]any{"name": "alice"})
}
func WriteJSONStatus ¶
func WriteJSONStatus(w http.ResponseWriter, r *http.Request, status int, data interface{})
WriteJSONStatus 自定义 HTTP Status 的成功响应
Types ¶
type ErrorSanitizer ¶ added in v2.2.32
type ErrorSanitizer func(ctx context.Context, err *exception.ApiException, requestID string) *exception.ApiException
ErrorSanitizer 错误响应脱敏钩子。
- ctx:当前请求的 context(可从中读取 request_id、业务注入的运行时上下文等; 部分框架兜底路径可能传入 context.Background());
- err:即将被序列化为统一 Response 的 ApiException(可能携带后端原始错误文本);
- requestID:本次请求的追踪 ID(可能为空串;与 ctx 中的值同源,保留参数便于 无 ctx 场景使用);
- 返回值:实际序列化的 ApiException;返回 nil 表示不修改,按原 err 输出。
type PanicReporter ¶ added in v2.2.44
PanicReporter panic 恢复时的日志回调。
- ctx:请求上下文(可从中取 request_id 等追踪字段);
- rec:recover() 捕获到的原始 panic 值。
业务侧可在此用自有 logger + runtime/debug.Stack() 记录完整调用栈。 响应体已默认脱敏(不携带 panic 值),排障信息应经由本回调落日志,而非泄漏给客户端。
type Response ¶
type Response struct {
Code int `json:"code"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
RequestID string `json:"request_id,omitempty"`
ServerTime int64 `json:"server_time"`
}
Response 统一响应体
ServerTime 为服务端生成该响应时的 Unix 秒级时间戳,便于客户端做时钟校准、 日志对齐或排障时确认响应时刻。之所以叫 ServerTime 而不是 RequestTime: 响应体里的时间字段表达的是"服务端产出响应的时刻",不是"请求发起的时刻", 这也是业内通用命名(淘宝 / 微信 / AWS 等开放平台)。
type TracingRoundTripper ¶
type TracingRoundTripper struct {
Base http.RoundTripper
// HeaderMap 将 trace 字段名 → HTTP Header 名
// 为 nil 时使用默认映射(request_id → X-Request-Id 等)
HeaderMap map[string]string
}
TracingRoundTripper 把 ctx 中所有追踪字段透传为请求头
func NewTracingRoundTripper ¶
func NewTracingRoundTripper(base http.RoundTripper) *TracingRoundTripper
NewTracingRoundTripper 构造默认的透传 RoundTripper