[Fiber]基于FastHttp引擎的高性能Web Framework简单入门

1,097 阅读10分钟

ChatGPT Image 2025年12月29日 11_39_09.png

简单介绍

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 启发。

image.png

路由组

路径

像路由一样,路由组也可以有属于一簇的路径。

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 接口包含了一个 LoadRender 方法,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,
    })

    # ...
}