简单介绍
Fiber 是一个受著名的 Node.js 的 Web Framework —— Express.js 启发的 Web 框架,建立在 Fasthttp 之上,Fasthttp 是 Go 最快的 HTTP 引擎。旨在简化快速开发,同时考虑零内存分配和性能。
安装
首先,需要下载并安装 Go SDK 1.17 或更高版本。
输入以下命令安装 Fiber:
go get github.com/gofiber/fiber/v2
零分配
Fiber 返回的某些值默认情况下,*fiber.Ctx 不是不可变的,可以在请求中重复使用。而且只能在处理程序中使用上下文值,你不能保留任何它们的引用。当你从 handler 程序返回之后,任何你从上下文中获取的值都可以在将来的请求中重复使用。实例如下:
func handler(c *fiber.Ctx) error {
// Variable is only valid within this handler
result := c.Params("foo")
// ...
}
如果需要在 handler 外部对这样值进行持久化,使用内置的拷贝函数拷贝它们的缓冲区。示例如下:
func handler(c *fiber.Ctx) error {
// Variable is only valid within this handler
result := c.Params("foo")
// Make a copy
buffer := make([]byte, len(result))
copy(buffer, result)
resultCopy := string(buffer)
// Variable is now valid forever
// ...
}
以下示例编写了一个函数,在 gofiber/utils.CopyString 函数下是可以用的。
app.Get("/:foo", func(c *fiber.Ctx) error {
// Variable is now immutable
result := utils.CopyString(c.Params("foo"))
// ...
})
你也可以通过设置选项让上下文返回的值不可变,这会让你在任何地方持久化它们。当然,这会以性能作为代价。
app := fiber.New(fiber.Config{
Immutable: true,
})
第一个Hello World
使用 Fiber 内置的 New 函数可以快速构建一个 Web App:
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}
输入以下命令启动应用程序:
go run server.go
通过浏览器访问 http://localhost:3000 返回 Hello, World! 字符串。
具体使用案例
路由
处理器
注册 HTTP 请求方式对应的处理函数:
// HTTP methods
func (app *App) Get(path string, handlers ...Handler) Router
func (app *App) Head(path string, handlers ...Handler) Router
func (app *App) Post(path string, handlers ...Handler) Router
func (app *App) Put(path string, handlers ...Handler) Router
func (app *App) Delete(path string, handlers ...Handler) Router
func (app *App) Connect(path string, handlers ...Handler) Router
func (app *App) Options(path string, handlers ...Handler) Router
func (app *App) Trace(path string, handlers ...Handler) Router
func (app *App) Patch(path string, handlers ...Handler) Router
// Add allows you to specifiy a method as value
func (app *App) Add(method, path string, handlers ...Handler) Router
// All will register the route on all HTTP methods
// Almost the same as app.Use but not bound to prefixes
func (app *App) All(path string, handlers ...Handler) Router
examples:
// Simple GET handler
app.Get("/api/list", func(c *fiber.Ctx) error {
return c.SendString("I'm a GET request!")
})
// Simple POST handler
app.Post("/api/register", func(c *fiber.Ctx) error {
return c.SendString("I'm a POST request!")
})
Use 函数可以用于中间件包和前缀捕捉器。这些路由将仅仅匹配路径的起始字符串。/john 将会匹配 /john.doe、/johnnnnn 等。
func (app *App) Use(args ...interface{}) Router
examples:
// Match any request
app.Use(func(c *fiber.Ctx) error {
return c.Next()
})
// Match request starting with /api
app.Use("/api", func(c *fiber.Ctx) error {
return c.Next()
})
// Match requests starting with /api or /home (multiple-prefix support)
app.Use([]string{"/api", "/home"}, func(c *fiber.Ctx) error {
return c.Next()
})
// Attach multiple handlers
app.Use("/api", func(c *fiber.Ctx) error {
c.Set("X-Custom-Header", random.String(32))
return c.Next()
}, func(c *fiber.Ctx) error {
return c.Next()
})
路由路径
和请求方法绑定到一起的路由路径定义了请求可以在哪里被处理。路由路径可以是固定的字符串或者是模式串。
examples:
// This route path will match requests to the root route, "/":
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("root")
})
// This route path will match requests to "/about":
app.Get("/about", func(c *fiber.Ctx) error {
return c.SendString("about")
})
// This route path will match requests to "/random.txt":
app.Get("/random.txt", func(c *fiber.Ctx) error {
return c.SendString("random.txt")
})
和 Express.js 框架一样,路由声明的顺序也发挥重要的作用。当一个请求被接收到,路由将会按照它们被声明的顺序进行检查。
请求参数
路由参数在路由中是动态元素,可以是具名或者是不具名的的部分。这些部分用于捕获 URL 中它们对应位置的值。以路径中具体的参数名位键,使用 Params 函数可以检索得到的值。
三种用于描述参数字符::、+、*。贪心参数由通配符 * 或者 加号 + 表示。
路由规则还提供了使用可选参数的可能性,因为有的具名参数被标记了 ? 符号,不像 + 号它不是可选的。你可以使用通配符作为一个可选的贪婪的范围参数。
examples:
// Parameters
app.Get("/user/:name/books/:title", func(c *fiber.Ctx) error {
fmt.Fprintf(c, "%s\n", c.Params("name"))
fmt.Fprintf(c, "%s\n", c.Params("title"))
return nil
})
// Plus - greedy - not optional
app.Get("/user/+", func(c *fiber.Ctx) error {
return c.SendString(c.Params("+"))
})
// Optional parameter
app.Get("/user/:name?", func(c *fiber.Ctx) error {
return c.SendString(c.Params("name"))
})
// Wildcard - greedy - optional
app.Get("/user/*", func(c *fiber.Ctx) error {
return c.SendString(c.Params("*"))
})
// This route path will match requests to "/v1/some/resource/name:customVerb",
// since the parameter character is escaped
app.Get("/v1/some/resource/name\\:customVerb", func(c *fiber.Ctx) error {
return c.SendString("Hello, Community")
})
// GET /@v1
// Params: "sign" -> "@", "param" -> "v1"
app.Get("/:sign:param", handler)
// GET /api-v1
// Params: "name" -> "v1"
app.Get("/api-:name", handler)
// GET /customer/v1/cart/proxy
// Params: "*1" -> "customer/", "*2" -> "/cart"
app.Get("/*v1*/proxy", handler)
// GET /v1/brand/4/shop/blue/xs
// Params: "*1" -> "brand/4", "*2" -> "blue/xs"
app.Get("/v1/*/shop/*", handler)
参数约束
对应的参数还需要进行校验,如果跟对应的路由参数不匹配,Fiber 直接返回 404。这个特性是受到了 .NET Core 启发。

路由组
路径
像路由一样,路由组也可以有属于一簇的路径。
func main() {
app := fiber.New()
api := app.Group("/api", middleware) // /api
v1 := api.Group("/v1", middleware) // /api/v1
v1.Get("/list", handler) // /api/v1/list
v1.Get("/user", handler) // /api/v1/user
v2 := api.Group("/v2", middleware) // /api/v2
v2.Get("/list", handler) // /api/v2/list
v2.Get("/user", handler) // /api/v2/user
log.Fatal(app.Listen(":3000"))
}
一组路径可以有一个可选的处理器:
func main() {
app := fiber.New()
api := app.Group("/api") // /api
v1 := api.Group("/v1") // /api/v1
v1.Get("/list", handler) // /api/v1/list
v1.Get("/user", handler) // /api/v1/user
v2 := api.Group("/v2") // /api/v2
v2.Get("/list", handler) // /api/v2/list
v2.Get("/user", handler) // /api/v2/user
log.Fatal(app.Listen(":3000"))
}
组处理器
组处理器可以被用于作为路由路径但是它们必须得有一个 Next 方法这样的话流程才能继续走下去。
func main() {
app := fiber.New()
handler := func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
}
api := app.Group("/api") // /api
v1 := api.Group("/v1", func(c *fiber.Ctx) error { // middleware for /api/v1
c.Set("Version", "v1")
return c.Next()
})
v1.Get("/list", handler) // /api/v1/list
v1.Get("/user", handler) // /api/v1/user
log.Fatal(app.Listen(":3000"))
}
模板
Fiber 提供了一个 Views 接口可以为你提供你自己的模板引擎。
type Views interface {
Load() error
Render(io.Writer, string, interface{}, ...string) error
}
Views 接口包含了一个 Load 和 Render 方法,Load 在应用程序初始化的时候被 Fiber 执行以加载或者解析模板。
// Pass engine to Fiber's Views Engine
app := fiber.New(fiber.Config{
Views: engine,
// Views Layout is the global layout for all template render until override on Render function.
ViewsLayout: "layouts/main"
})
Render 方法被链接到了 ctx.Render(),并且接收一个模板名称和绑定的数据。如果在 Render 函数中没有定义布局,它会使用全局的布局。如果 Fiber 配置项 PassLocalToViews 被开启了,那么所有使用 ctx.Locals(key, value) 本地变量设置都会传递给模板。
app.Get("/", func(c *fiber.Ctx) error {
return c.Render("index", fiber.Map{
"hello": "world",
});
})
引擎
Fiber 团队维护者模板包用来给模板引擎提供包装器:
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html"
)
func main() {
// Initialize standard Go html template engine
engine := html.New("./views", ".html")
// If you want other engine, just replace with following
// Create a new engine with django
// engine := django.New("./views", ".django")
app := fiber.New(fiber.Config{
Views: engine,
})
app.Get("/", func(c *fiber.Ctx) error {
// Render index template
return c.Render("index", fiber.Map{
"Title": "Hello, World!",
})
})
log.Fatal(app.Listen(":3000"))
}
错误处理
捕捉错误
在运行路由处理器和中间件时确保 Fiber 能捕捉到所有的错误是很重要的。你必须得把这些错误返回给在Fiber 能捕捉并且能处理的地方的处理器函数。
examples:
app.Get("/", func(c *fiber.Ctx) error {
// Pass error to Fiber
return c.SendFile("file-does-not-exist")
})
默认情况下, Fiber 不会直接处理 panic。为了能让任意处理器从 panic 函数中恢复,你必须像下面这样在中间件中包含 Recover 函数。
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
app := fiber.New()
app.Use(recover.New())
app.Get("/", func(c *fiber.Ctx) error {
panic("This panic is caught by fiber")
})
log.Fatal(app.Listen(":3000"))
}
默认的错误处理器
Fiber 提供了一个默认的错误处理器。对于一个标准的错误,响应体被作为 500 服务器错误发送出去。如果那个错误的类型是 fiber.Error,响应体将携带自定义的状态码和消息体然后被发送出去。
// Default error handler
var DefaultErrorHandler = func(c *fiber.Ctx, err error) error {
// Status code defaults to 500
code := fiber.StatusInternalServerError
// Retrieve the custom status code if it's a *fiber.Error
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
}
// Set Content-Type: text/plain; charset=utf-8
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
// Return status code with error message
return c.Status(code).SendString(err.Error())
}
自定义的错误处理器
在初始化一个 Fiber 实例时,可以使用一个 Config 对象来设置一个自定义的错误处理器。
在大多数情况下,默认的错误处理器是足够多的。然而,如果你想捕捉不同类型的错误并且采取相应的行动一个自定义的错误处理器可以派上用场。例如,像中心系统发送一个通知邮件或者打印错误日志。你也可以向客户端发送自定义的响应体,例如,错误页面或者仅仅是 JSON 响应体。
examples:
// Create a new fiber instance with custom config
app := fiber.New(fiber.Config{
// Override default error handler
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
// Status code defaults to 500
code := fiber.StatusInternalServerError
// Retrieve the custom status code if it's a *fiber.Error
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
}
// Send custom error page
err = ctx.Status(code).SendFile(fmt.Sprintf("./%d.html", code))
if err != nil {
// In case the SendFile fails
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
// Return from handler
return nil
},
})
// ...
校验
Fiber 可以很好的使用 validator 包以确保要存储的数据是校验正确的。
type Job struct{
Type string `validate:"required,min=3,max=32"`
Salary int `validate:"required,number"`
}
type User struct{
Name string `validate:"required,min=3,max=32"`
// use `*bool` here otherwise the validation will fail for `false` values
// Ref: https://github.com/go-playground/validator/issues/319#issuecomment-339222389
IsActive *bool `validate:"required"`
Email string `validate:"required,email,min=6,max=32"`
Job Job `validate:"dive"`
}
type ErrorResponse struct {
FailedField string
Tag string
Value string
}
var validate = validator.New()
func ValidateStruct(user User) []*ErrorResponse {
var errors []*ErrorResponse
err := validate.Struct(user)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
var element ErrorResponse
element.FailedField = err.StructNamespace()
element.Tag = err.Tag()
element.Value = err.Param()
errors = append(errors, &element)
}
}
return errors
}
func AddUser(c *fiber.Ctx) error {
//Connect to database
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
})
}
errors := ValidateStruct(*user)
if errors != nil {
return c.Status(fiber.StatusBadRequest).JSON(errors)
}
//Do something else here
//Return user
return c.JSON(user)
}
// Running a test with the following curl commands
// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"isactive\":\"True\"}" http://localhost:8080/register/user
// Results in
// [{"FailedField":"User.Email","Tag":"required","Value":""},{"FailedField":"User.Job.Salary","Tag":"required","Value":""},{"FailedField":"User.Job.Type","Tag":"required","Value":""}]⏎
钩子函数
从 Fiber v2.30.0 开始,当运行一些方法时你可以执行自定义的用户函数。下列是这些钩子函数的列表:
执行钩子函数时,都可以通过路由参数获取路由属性,
Constants
// Handlers define a function to create hooks for Fiber.
type OnRouteHandler = func(Route) error
type OnNameHandler = OnRouteHandler
type OnGroupHandler = func(Group) error
type OnGroupNameHandler = OnGroupHandler
type OnListenHandler = func() error
type OnForkHandler = func(int) error
type OnShutdownHandler = OnListenHandler
type OnMountHandler = func(*App) error
OnRoute
OnRoute 是一个在每一个路由注册时执行用户的函数的钩子。你也可以通过路由参数获取路由属性。
func (app *App) OnRoute(handler ...OnRouteHandler)
OnName
OnName 是一个在每一个路由命名时执行用户函数的钩子。
func (app *App) OnName(handler ...OnNameHandler)
examples:
package main
import (
"fmt"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString(c.Route().Name)
}).Name("index")
app.Hooks().OnName(func(r fiber.Route) error {
fmt.Print("Name: " + r.Name + ", ")
return nil
})
app.Hooks().OnName(func(r fiber.Route) error {
fmt.Print("Method: " + r.Method + "\n")
return nil
})
app.Get("/add/user", func(c *fiber.Ctx) error {
return c.SendString(c.Route().Name)
}).Name("addUser")
app.Delete("/destroy/user", func(c *fiber.Ctx) error {
return c.SendString(c.Route().Name)
}).Name("destroyUser")
app.Listen(":5000")
}
// Results:
// Name: addUser, Method: GET
// Name: destroyUser, Method: DELETE
OnGroup
OnGroup 是一个在每一个路由组注册时执行用户函数的钩子。
func (app *App) OnGroup(handler ...OnGroupHandler)
OnGroupName
OnGroupName 是一个在每一个路由组命名时执行用户函数的钩子。
func (app *App) OnGroupName(handler ...OnGroupNameHandler)
OnListen
OnListen 是在监听时执行用户函数的钩子。
func (app *App) OnListen(handler ...OnListenHandler)
OnFork
OnFork 是一个在 Fork 时执行用户函数的钩子。
func (app *App) OnFork(handler ...OnForkHandler)
OnShutdown
OnShutdown 是一个调用 Shutdown 之后执行用户函数的钩子。
func (app *App) OnShutdown(handler ...OnShutdownHandler)
OnMount
OnMount 是一个在挂载操作之后执行用户函数的钩子。当子应用挂载到了一个父应用时挂载事件被解除。父应用将作为一个参数被传递。它在应用和路由组挂载时起作用。
func (h *Hooks) OnMount(handler ...OnMountHandler)
examples:
package main
import (
"fmt"
"github.com/gofiber/fiber/v2"
)
func main() {
app := New()
app.Get("/", testSimpleHandler).Name("x")
subApp := New()
subApp.Get("/test", testSimpleHandler)
subApp.Hooks().OnMount(func(parent *fiber.App) error {
fmt.Print("Mount path of parent app: "+parent.MountPath())
// ...
return nil
})
app.Mount("/sub", subApp)
}
// Result:
// Mount path of parent app:
变得更快
自定义的 JSON 编码器/解析器
从 Fiber v2.32.0 开始,我们因稳定性和生产效率使用 encoding/json 包作为默认的 json 库。然后,标准库相比于第三方库有些慢。如果你对标准库的性能不满意,那么推荐你使用以下第三方库:
package main
import "github.com/gofiber/fiber/v2"
import "github.com/goccy/go-json"
func main() {
app := fiber.New(fiber.Config{
JSONEncoder: json.Marshal,
JSONDecoder: json.Unmarshal,
})
# ...
}