Gin Handler 与 http.Handler 互转/混用
结论先行
方向一:gin → net/http。gin.Engine 本身实现了 http.Handler,直接 http.ListenAndServe(addr, engine) 或 srv.Handler = engine,还能和标准库 mux 混用。方向二:http.Handler → gin。用 gin.WrapH(h) 包装任意 http.Handler、gin.WrapF(f) 包装 http.HandlerFunc,转成 gin.HandlerFunc 塞进中间件或路由。反向把单个 gin.HandlerFunc 塞进外部 http.Handler 不常见,通常整引擎当 Handler 用即可。
对照表
| 转换 | 手段 |
|---|---|
| http.Handler → gin.HandlerFunc | gin.WrapH(h) |
| http.HandlerFunc → gin.HandlerFunc | gin.WrapF(f) |
| gin.Engine → http.Handler | 不需要转,Engine 已实现 ServeHTTP |
| 与 ServeMux 混用 | mux.Handle("/", engine),其余路径留给 mux |
代码示例
// 1) 标准库 handler/handlerFunc 收编进 gin
func oldHealth(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
func main() {
r := gin.New()
r.Use(gin.WrapH(someHTTPMiddleware())) // 整个 http.Handler 当中间件
r.GET("/healthz", gin.WrapF(oldHealth)) // http.HandlerFunc 直接挂路由
// 业务路由...
// 2) 标准库 mux 与 gin 混用(Go 1.22+ 的 ServeMux 也适用)
mux := http.NewServeMux()
mux.HandleFunc("/metrics", metricsHandler) // 系统类接口走标准库
mux.Handle("/", r) // 其余全部交给 gin
_ = http.ListenAndServe(":8080", mux)
}
注意点
- WrapH/WrapF 包进来的 handler 在 gin 的链内执行:Recovery、日志中间件对它们同样生效(panic 会被外层 Recovery 捕获)。
- 把 engine 挂到 mux 子路径(如 mux.Handle("/api/", engine))时,gin 收到的 URL.Path 仍带 /api 前缀,需要 gin 侧路由也带该前缀,否则全 404——通常建议 gin 占根路径。
- 不要手工拼一个 *gin.Context 塞给外部调用:它的生命周期、池化、Keys 都绑定 gin 的请求流程(见第 12 章)。
常见追问 / 记忆点
- 追问:为什么 gin handler 不能直接当 http.Handler 用?答:签名不同——gin.HandlerFunc 是 func(*gin.Context),http.Handler 是 ServeHTTP(http.ResponseWriter, *http.Request),需要适配层(WrapH/WrapF 就是干这个的)。
- 追问:迁移老项目到 Gin 怎么渐进?答:新路由用 gin,旧 http.Handler 用 WrapH 挂进来;或反过来 mux 占根、gin 占子前缀(注意前缀问题)。
- 记忆点:engine 本身就是 http.Handler;收编标准库中间件用 WrapH/WrapF;混用时盯紧 URL 前缀。