Fiber v3 的 Express 风格 handler:两参签名、适配层开销与 Go 1.26 的 unsafe 限制
Fiber 是跑在 Fasthttp 之上的 Go Web 框架,设计目标对标 Express:把 Node.js 那一套路由和中间件写法搬到 Go 里,同时保留 Go 的性能。项目在 github.com/gofiber/fiber,官网 gofiber.io,完整文档在 docs.gofiber.io,v3 的 API 参考见 pkg.go.dev/github.com/gofiber/fiber/v3。
安装要求 Go 1.26 或更高:
go mod init github.com/your/repo
go get -u github.com/gofiber/fiber/v3
最小可跑的例子:
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World 👋!")
})
log.Fatal(app.Listen(":3000"))
Express 风格在 Go 里到底意味着什么
Express 的 handler 签名是 (req, res),中间件是 (req, res, next),响应通过 res 对象的 send、status、json 等方法完成,出错走 next(err)。Go 标准库是 (w http.ResponseWriter, r *http.Request),而 Fiber 原生写法只有一个参数 func(c fiber.Ctx) error,把错误作为返回值。
Fiber v3 的做法是让 router 同时接受这几种形态,注册时自动适配。这样从 Express 风格代码库迁过来的中间件和路由处理函数可以基本照搬,同时继续用 Fiber 自己的路由能力。
三种 handler 签名
原生形式,fiber.Ctx 单参数:
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World 👋!")
})
Express 风格的请求/响应两参形式,操作轻量的 fiber.Req 和 fiber.Res 接口:
// 请求/响应处理函数(2 参数)
app.Get("/", func(req fiber.Req, res fiber.Res) error {
return res.SendString("Hello from Express-style handlers!")
})
三参中间件有两种 next 变体。一种 next 返回 error,用于把错误沿链路上抛:
// 带 error 返回值的 next(3 参数)
app.Use(func(req fiber.Req, res fiber.Res, next func() error) error {
if req.IP() == "192.168.1.254" {
return res.SendStatus(fiber.StatusForbidden)
}
return next()
})
另一种 next 无参数,适合不需要传播错误的场景,直接 return 即可中断链路:
// 无参数 next(3 参数)
app.Use(func(req fiber.Req, res fiber.Res, next func()) {
if req.Get("X-Skip") == "true" {
return // 不调用 next,链条到此为止
}
next()
})
Middleware、Next 顺序与 Group 分组
原生写法的中间件按注册顺序执行,c.Next() 决定是否继续往下走:
app.Use(func(c fiber.Ctx) error {
fmt.Println("First handler")
return c.Next()
})
app.Use("/api", func(c fiber.Ctx) error {
fmt.Println("Second handler")
return c.Next()
})
app.Get("/api/list", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
路由支持通配和参数,命名路由可以用 GetRoute 反查方法、名称、路径和参数:
app.Get("/api/*", func(c fiber.Ctx) error {
msg := fmt.Sprintf("✋ %s", c.Params("*"))
return c.SendString(msg)
})
app.Get("/flights/:from-:to", ...)
app.Get("/:file.:ext", ...)
app.Get("/:name/:age/:gender?", ...)
app.Get("/api/*", h).Name("api")
route := app.GetRoute("api")
Group 用来给一组路由挂同一批中间件:
api := app.Group("/api", middleware)
v1 := api.Group("/v1", middleware)
v1.Get("/list", handler)
静态文件走 static 中间件:app.Get("/*", static.New("./public")),单页应用用 app.Get("*", static.New("./public/index.html"))。常用的还有 logger.New()、cors.New()、recover.New()、404 兜底 c.SendStatus(fiber.StatusNotFound)、c.JSON(&User{"John", 20})、WebSocket 的 websocket.New(func(c *websocket.Conn){...}),以及 SSE 的 c.Context().SetBodyStreamWriter(...)。模板默认用 html/template,template 包另外支持 amber、handlebars、mustache、pug:
app := fiber.New(fiber.Config{Views: pug.New("./views", ".pug")})
app.Get("/", func(c fiber.Ctx) error {
return c.Render("home", fiber.Map{"title": "Homepage", "year": 1999})
})
net/http 兼容与 adaptor
Fiber 可以和标准库并排跑。router 直接接受现成的 net/http handler,连原生 fasthttp.RequestHandler 回调也能注册:
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte("served by net/http")); err != nil {
panic(err)
}
})
app := fiber.New()
app.Get("/", httpHandler)
log.Fatal(app.Listen(":3000"))
需要整体转换应用或复用 net/http 中间件链时,用 adaptor 中间件,它支持双向转换 handler 和 middleware,也能把 Fiber app 挂进 net/http server。
取舍
fiber.Ctx 的 zero allocation 语义。 为了性能,从 fiber.Ctx 拿到的值默认不是不可变的,会在请求之间复用。规则是:上下文里的值只在 handler 内使用,不能保留任何引用。一旦从 handler 返回,这些值就会被后续请求复用,跨请求保存会读到别的请求的数据。
适配层有额外开销。 适配后的 net/http handler 继续按标准库语义工作,拿不到 fiber.Ctx 的能力,同时要付出兼容层的开销。所以追求性能的路径仍然是原生 fiber.Handler 回调。Express 风格的两参/三参签名同样经过适配层,迁移方便和零开销之间只能取一头。适配层在高并发下的具体损耗没有实测数字,benchmarks 页面收录的是框架整体对比,不单独拆适配成本。
unsafe 与 Go 版本限制。 Fiber 内部用了 unsafe,因此不一定总是兼容最新的 Go 版本。v3 的测试基线是 Go 1.26 或更高,go get 之前先确认工具链版本,否则会在编译期直接失败。