Go Web 框架选型:Go 1.22 之后,net/http 兼容性才是那条分界线
Go 1.22 给 net/http.ServeMux 补上了方法匹配、路径参数和通配符。选框架的第一刀因此不再是性能跑分,而是「它到底在不在 net/http 上」。落在 net/http 这条线上的框架,可以直接复用标准中间件、http.Server 的超时配置和整条可观测链路;不落在上面的(目前主要是 Fiber)就得接受一套平行生态。下面按这条轴拆各个框架,再给可执行的取舍。
项目信息:
- Go 官方 routing enhancements 博客:https://go.dev/blog/routing-enhancements
- Chi:https://github.com/go-chi/chi/v5
- Gin:https://github.com/gin-gonic/gin
- Echo:https://github.com/labstack/echo
- Fiber:https://github.com/gofiber/fiber
- 标准库文档:https://pkg.go.dev
Go 1.22 之后,ServeMux 能做什么
mux := http.NewServeMux()
mux.HandleFunc("GET /books/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
})
{id}匹配单个路径段,{path...}吃掉剩余部分,{$}表示精确匹配到结尾。- 优先级按精确度判定,越具体的 pattern 赢。
- 重叠且无法判定谁更具体的 pattern,在注册时直接 panic,不是等到运行时才暴露。
- 方法不匹配返回 405,并带上
Allow头。
ServeMux 仍然没有的是:路由分组、中间件辅助、请求绑定、校验。这些要么自己写,要么换一层薄封装。
Chi:100% net/http 兼容,补的是分组和中间件
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Route("/books", func(r chi.Router) {
r.Get("/{id}", getBook)
})
id := chi.URLParam(r, "id")
Chi 的 handler 就是 http.HandlerFunc,中间件就是 func(http.Handler) http.Handler。标准库和第三方 net/http 中间件可以直接挂上来,不需要适配层。核心 router 不到 1000 行;docgen 能把路由表打印出来。它本身不含 binding、validation、渲染,这些都留给上层。模块路径 github.com/go-chi/chi/v5。
Gin v1.12:radix tree、*gin.Context,以及 c.Copy()
type createBook struct {
Title string `json:"title" binding:"required"`
}
func create(c *gin.Context) {
var in createBook
if err := c.ShouldBindJSON(&in); err != nil {
// ...
}
c.JSON(201, book)
}
Gin 用自研 httprouter(radix tree)查找,复杂度 O(k),官方称每请求零堆分配。*gin.Context 把参数解析、响应写入、路径参数、KV 存储收在一个对象里;binding 和 validation 走 go-playground/validator,binding:"required" 这种 tag 直接写在结构体上。gin.Default() 默认带 Logger 和 Recovery。*gin.Engine 实现 http.Handler,所以能塞进自己的 http.Server 配超时;gin.WrapH / gin.WrapF 把标准 handler 接进来;中间件是 gin.HandlerFunc,靠 c.Next() / c.Abort() 控制流程。Gin 大约占 Go Web 框架使用量的一半。
坑在 *gin.Context:它不是 goroutine 安全的。在 handler 里起了 goroutine 还继续用 c,会踩到 context 复用导致的数据竞争——开发时单请求跑得通,并发一上来就炸。要在 goroutine 里用,先 c.Copy()。
Echo v5:handler 返回 error,错误处理收口
e := echo.New()
e.Use(middleware.RequestLogger(), middleware.Recover())
e.GET("/books/:id", func(c echo.Context) error {
return c.JSON(200, book)
})
Echo 的 handler 签名返回 error,错误统一交给集中式 error handler。相比「不小心把响应写两次」这类误用,返回 error 的签名更难写错。它有自己的 Context(不是标准库那个)。第一方中间件覆盖 Logger、Recover、CORS、JWT、限流、gzip、request ID、body limit;Validator 接口默认接到 go-playground/validator。echo.WrapHandler / echo.WrapMiddleware 可以把标准库 handler 和中间件适配进来。自动 Let's Encrypt 证书和 HTTP/2 都有。版本上,Echo v5 于 2026 年 1 月发布;v4 会继续收到安全修复,直到 2026 年 12 月 31 日。v4 的模块路径是 github.com/labstack/echo/v4。
Fiber v3:基于 fasthttp,不在 net/http 这条线上
app := fiber.New()
app.Get("/books/:id", func(c fiber.Ctx) error {
// ...
})
Fiber 的 handler 接收 fiber.Ctx 接口,返回 error,API 模仿 Express。它建立在 fasthttp 之上,不是 http.Handler。fasthttp 会池化 request/response 对象,官方在热点路径上宣称最高 10 倍吞吐。代价:
c.Params()返回的值只在 handler 生命周期内有效,之后会被下一个请求复用,直接存下来读到的会是别人的数据。要用就先拷贝,或者打开Immutable设置。- 不兼容 net/http,所有依赖
http.Handler的中间件和工具都用不上——gqlgen、OpenTelemetry 的 HTTP instrumentation、swagger 生成都会更麻烦。适配中间件可以双向转换,但适配后的 handler 更慢。 - fasthttp 目前没有 HTTP/2(官方列为进行中)。需要 HTTP/2 就在 Nginx、Cloudflare 这类反代上终结。
Fiber v3.0.0 于 2026 年 2 月发布。
怎么选
- 库、极小服务、想零依赖 →
net/http.ServeMux(Go 1.22+)。 - 想用标准类型,但需要路由分组和按路由挂中间件 → Chi。
- 团队熟 Gin,或者想把绑定 + 校验一次调用搞定 → Gin(记得
c.Copy())。 - handler 返回 error、想复用 net/http 中间件 → Echo。
- 从 Express 过来,并且在「小响应、高并发」场景量过确实有 fasthttp 收益,同时愿意放弃 HTTP/2 和 net/http 生态 → Fiber。
- Contract-first / OpenAPI → 在选定的 router 上加 Huma。
默认路线可以很朴素:先上 net/http,不够再上 Chi。路由本身从来不是瓶颈,radix tree 也好、前缀树也好,都够快;长期成本取决于 HTTP 基础、handler 签名和生态适配。业务逻辑别塞进 handler。