3.2.4 Fiber 框架入门

3.2.4 Fiber 框架入门 #

Fiber 是一个受 Express.js 启发的 Go Web 框架,以其极高的性能和熟悉的 API 设计而著称。本节将深入介绍 Fiber 框架的特点、使用方法和最佳实践。

Fiber 框架特点与优势 #

核心特性 #

1. 极致性能

  • 基于 fasthttp 构建,性能优于标准库
  • 零内存分配的路由系统
  • 高效的内存池管理

2. Express.js 风格 API

  • 熟悉的链式调用语法
  • 直观的中间件系统
  • 简洁的路由定义

3. 丰富的内置功能

  • 内置模板引擎支持
  • WebSocket 支持
  • 静态文件服务
  • 压缩和缓存

性能优势 #

// Fiber 的高性能体现在其优化的设计中
package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/logger"
    "github.com/gofiber/fiber/v2/middleware/recover"
)

func main() {
    // 创建 Fiber 应用
    app := fiber.New(fiber.Config{
        // 启用预分叉模式以提升性能
        Prefork: true,
        // 禁用启动消息以减少输出
        DisableStartupMessage: true,
        // 启用压缩
        CompressedFileSuffix: ".gz",
    })

    // 中间件
    app.Use(logger.New())
    app.Use(recover.New())

    // 路由
    app.Get("/", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "message": "Hello, Fiber!",
            "fast":    true,
        })
    })

    // 启动服务器
    app.Listen(":3000")
}

Express.js 风格的 API #

基础路由定义 #

package main

import (
    "github.com/gofiber/fiber/v2"
    "strconv"
)

func main() {
    app := fiber.New()

    // GET 路由
    app.Get("/", homeHandler)

    // POST 路由
    app.Post("/users", createUserHandler)

    // PUT 路由
    app.Put("/users/:id", updateUserHandler)

    // DELETE 路由
    app.Delete("/users/:id", deleteUserHandler)

    // PATCH 路由
    app.Patch("/users/:id", patchUserHandler)

    // 处理所有 HTTP 方法
    app.All("/ping", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "method": c.Method(),
            "path":   c.Path(),
        })
    })

    // 静态文件服务
    app.Static("/", "./public")
    app.Static("/api", "./docs")

    app.Listen(":3000")
}

func homeHandler(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{
        "message": "Welcome to Fiber API",
        "version": "2.0.0",
    })
}

func createUserHandler(c *fiber.Ctx) error {
    // 解析 JSON 请求体
    var user User
    if err := c.BodyParser(&user); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Cannot parse JSON",
        })
    }

    // 创建用户逻辑
    createdUser := createUser(user)
    return c.Status(201).JSON(createdUser)
}

路径参数和查询参数 #

func setupParameterHandling(app *fiber.App) {
    // 路径参数
    app.Get("/users/:id", func(c *fiber.Ctx) error {
        id := c.Params("id")
        userID, err := strconv.Atoi(id)
        if err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid user ID",
            })
        }

        user := getUserByID(userID)
        return c.JSON(user)
    })

    // 多个路径参数
    app.Get("/users/:id/posts/:postId", func(c *fiber.Ctx) error {
        userID := c.Params("id")
        postID := c.Params("postId")

        return c.JSON(fiber.Map{
            "user_id": userID,
            "post_id": postID,
        })
    })

    // 可选参数
    app.Get("/files/:filename?", func(c *fiber.Ctx) error {
        filename := c.Params("filename", "default.txt")
        return c.JSON(fiber.Map{
            "filename": filename,
        })
    })

    // 通配符参数
    app.Get("/download/*", func(c *fiber.Ctx) error {
        filepath := c.Params("*")
        return c.JSON(fiber.Map{
            "filepath": filepath,
        })
    })

    // 查询参数
    app.Get("/search", func(c *fiber.Ctx) error {
        query := c.Query("q")
        page := c.Query("page", "1")
        size := c.Query("size", "10")

        // 获取所有查询参数
        queries := c.Queries()

        return c.JSON(fiber.Map{
            "query":   query,
            "page":    page,
            "size":    size,
            "all":     queries,
        })
    })

    // 表单数据
    app.Post("/form", func(c *fiber.Ctx) error {
        name := c.FormValue("name")
        email := c.FormValue("email", "[email protected]")

        return c.JSON(fiber.Map{
            "name":  name,
            "email": email,
        })
    })
}

高性能特性 #

连接池和内存优化 #

func setupHighPerformance() *fiber.App {
    app := fiber.New(fiber.Config{
        // 启用预分叉模式
        Prefork: true,

        // 严格路由匹配
        StrictRouting: true,

        // 启用大小写敏感
        CaseSensitive: true,

        // 禁用默认日期头
        DisableDefaultDate: true,

        // 禁用默认内容类型
        DisableDefaultContentType: true,

        // 禁用头部规范化
        DisableHeaderNormalizing: true,

        // 自定义错误处理
        ErrorHandler: func(c *fiber.Ctx, err error) error {
            code := fiber.StatusInternalServerError
            if e, ok := err.(*fiber.Error); ok {
                code = e.Code
            }

            return c.Status(code).JSON(fiber.Map{
                "error": err.Error(),
            })
        },

        // 读取超时
        ReadTimeout: time.Second * 10,

        // 写入超时
        WriteTimeout: time.Second * 10,

        // 空闲超时
        IdleTimeout: time.Second * 120,

        // 请求体大小限制
        BodyLimit: 4 * 1024 * 1024, // 4MB
    })

    return app
}

缓存和压缩 #

import (
    "github.com/gofiber/fiber/v2/middleware/cache"
    "github.com/gofiber/fiber/v2/middleware/compress"
    "github.com/gofiber/fiber/v2/middleware/etag"
)

func setupOptimizations(app *fiber.App) {
    // 启用 ETag
    app.Use(etag.New())

    // 启用压缩
    app.Use(compress.New(compress.Config{
        Level: compress.LevelBestSpeed,
    }))

    // 启用缓存
    app.Use(cache.New(cache.Config{
        Next: func(c *fiber.Ctx) bool {
            // 跳过 POST、PUT、DELETE 请求
            return c.Method() != "GET"
        },
        Expiration:   30 * time.Minute,
        CacheControl: true,
    }))

    // 静态文件缓存
    app.Static("/static", "./public", fiber.Static{
        Compress:      true,
        ByteRange:     true,
        Browse:        false,
        CacheDuration: 24 * time.Hour,
    })
}

中间件与路由组 #

内置中间件 #

import (
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/helmet"
    "github.com/gofiber/fiber/v2/middleware/limiter"
    "github.com/gofiber/fiber/v2/middleware/logger"
    "github.com/gofiber/fiber/v2/middleware/monitor"
    "github.com/gofiber/fiber/v2/middleware/pprof"
    "github.com/gofiber/fiber/v2/middleware/recover"
    "github.com/gofiber/fiber/v2/middleware/requestid"
)

func setupBuiltinMiddleware(app *fiber.App) {
    // 恢复中间件
    app.Use(recover.New())

    // 请求 ID 中间件
    app.Use(requestid.New())

    // 日志中间件
    app.Use(logger.New(logger.Config{
        Format: `{"time":"${time}","method":"${method}","path":"${path}","status":${status},"latency":"${latency}","ip":"${ip}","user_agent":"${ua}"}` + "\n",
    }))

    // CORS 中间件
    app.Use(cors.New(cors.Config{
        AllowOrigins: "*",
        AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH",
        AllowHeaders: "Origin, Content-Type, Accept, Authorization",
    }))

    // 安全中间件
    app.Use(helmet.New())

    // 限流中间件
    app.Use(limiter.New(limiter.Config{
        Max:        100,
        Expiration: 1 * time.Minute,
        KeyGenerator: func(c *fiber.Ctx) string {
            return c.IP()
        },
        LimitReached: func(c *fiber.Ctx) error {
            return c.Status(429).JSON(fiber.Map{
                "error": "Rate limit exceeded",
            })
        },
    }))

    // 性能监控(仅开发环境)
    if fiber.IsChild() {
        app.Get("/metrics", monitor.New())
        app.Use(pprof.New())
    }
}

自定义中间件 #

// JWT 认证中间件
func jwtMiddleware(secret string) fiber.Handler {
    return func(c *fiber.Ctx) error {
        token := c.Get("Authorization")
        if token == "" {
            return c.Status(401).JSON(fiber.Map{
                "error": "Missing authorization header",
            })
        }

        // 移除 "Bearer " 前缀
        if len(token) > 7 && token[:7] == "Bearer " {
            token = token[7:]
        }

        // 验证 JWT token
        claims, err := validateJWT(token, secret)
        if err != nil {
            return c.Status(401).JSON(fiber.Map{
                "error": "Invalid token",
            })
        }

        // 将用户信息存储到上下文
        c.Locals("user", claims)
        return c.Next()
    }
}

// API 密钥中间件
func apiKeyMiddleware(validKeys []string) fiber.Handler {
    keyMap := make(map[string]bool)
    for _, key := range validKeys {
        keyMap[key] = true
    }

    return func(c *fiber.Ctx) error {
        apiKey := c.Get("X-API-Key")
        if apiKey == "" {
            return c.Status(401).JSON(fiber.Map{
                "error": "API key required",
            })
        }

        if !keyMap[apiKey] {
            return c.Status(401).JSON(fiber.Map{
                "error": "Invalid API key",
            })
        }

        return c.Next()
    }
}

// 请求验证中间件
func validationMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        // 验证 Content-Type
        if c.Method() == "POST" || c.Method() == "PUT" {
            contentType := c.Get("Content-Type")
            if !strings.Contains(contentType, "application/json") {
                return c.Status(400).JSON(fiber.Map{
                    "error": "Content-Type must be application/json",
                })
            }
        }

        return c.Next()
    }
}

// 审计日志中间件
func auditMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        start := time.Now()

        // 记录请求体
        var requestBody string
        if c.Method() == "POST" || c.Method() == "PUT" {
            requestBody = string(c.Body())
        }

        // 继续处理请求
        err := c.Next()

        // 记录审计信息
        auditLog := map[string]interface{}{
            "timestamp":    start,
            "method":       c.Method(),
            "path":         c.Path(),
            "ip":           c.IP(),
            "user_agent":   c.Get("User-Agent"),
            "request_body": requestBody,
            "status_code":  c.Response().StatusCode(),
            "duration":     time.Since(start),
        }

        // 异步记录日志
        go logAudit(auditLog)

        return err
    }
}

路由组 #

func setupRouteGroups(app *fiber.App) {
    // API v1 路由组
    v1 := app.Group("/api/v1")

    // 用户路由组
    users := v1.Group("/users")
    users.Get("/", getUsersHandler)
    users.Post("/", createUserHandler)
    users.Get("/:id", getUserHandler)
    users.Put("/:id", updateUserHandler)
    users.Delete("/:id", deleteUserHandler)

    // 用户子资源
    users.Get("/:id/posts", getUserPostsHandler)
    users.Post("/:id/posts", createUserPostHandler)

    // 文章路由组
    posts := v1.Group("/posts")
    posts.Get("/", getPostsHandler)
    posts.Post("/", createPostHandler)
    posts.Get("/:id", getPostHandler)
    posts.Put("/:id", updatePostHandler)
    posts.Delete("/:id", deletePostHandler)

    // 管理员路由组(需要认证)
    admin := app.Group("/admin")
    admin.Use(jwtMiddleware("your-secret-key"))
    admin.Use(func(c *fiber.Ctx) error {
        user := c.Locals("user").(map[string]interface{})
        if user["role"] != "admin" {
            return c.Status(403).JSON(fiber.Map{
                "error": "Admin access required",
            })
        }
        return c.Next()
    })

    admin.Get("/dashboard", adminDashboardHandler)
    admin.Get("/users", adminGetUsersHandler)
    admin.Delete("/users/:id", adminDeleteUserHandler)

    // 公共 API(需要 API 密钥)
    public := app.Group("/public")
    public.Use(apiKeyMiddleware([]string{"key1", "key2", "key3"}))
    public.Get("/stats", getPublicStatsHandler)
    public.Get("/health", getHealthHandler)
}

数据绑定和验证 #

请求数据处理 #

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name" validate:"required,min=2,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Age      int    `json:"age" validate:"required,min=18,max=120"`
    Password string `json:"password,omitempty" validate:"required,min=8"`
}

type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=2,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Age      int    `json:"age" validate:"required,min=18,max=120"`
    Password string `json:"password" validate:"required,min=8"`
}

func setupDataBinding(app *fiber.App) {
    // JSON 数据绑定
    app.Post("/users", func(c *fiber.Ctx) error {
        var req CreateUserRequest

        // 解析 JSON 请求体
        if err := c.BodyParser(&req); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid JSON format",
            })
        }

        // 验证数据
        if err := validateStruct(req); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Validation failed",
                "details": err.Error(),
            })
        }

        // 创建用户
        user := createUser(req)
        return c.Status(201).JSON(user)
    })

    // 查询参数绑定
    app.Get("/users", func(c *fiber.Ctx) error {
        page := c.QueryInt("page", 1)
        size := c.QueryInt("size", 10)
        sort := c.Query("sort", "id")

        // 验证参数
        if page < 1 {
            page = 1
        }
        if size < 1 || size > 100 {
            size = 10
        }

        users := getUsersWithPagination(page, size, sort)
        return c.JSON(fiber.Map{
            "data":  users,
            "page":  page,
            "size":  size,
            "total": getTotalUsers(),
        })
    })

    // 表单数据绑定
    app.Post("/form", func(c *fiber.Ctx) error {
        var form struct {
            Name  string `form:"name"`
            Email string `form:"email"`
            Age   int    `form:"age"`
        }

        if err := c.BodyParser(&form); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid form data",
            })
        }

        return c.JSON(form)
    })

    // 文件上传
    app.Post("/upload", func(c *fiber.Ctx) error {
        file, err := c.FormFile("file")
        if err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "No file uploaded",
            })
        }

        // 验证文件大小
        if file.Size > 10*1024*1024 { // 10MB
            return c.Status(400).JSON(fiber.Map{
                "error": "File too large",
            })
        }

        // 保存文件
        filename := fmt.Sprintf("uploads/%d_%s", time.Now().Unix(), file.Filename)
        if err := c.SaveFile(file, filename); err != nil {
            return c.Status(500).JSON(fiber.Map{
                "error": "Failed to save file",
            })
        }

        return c.JSON(fiber.Map{
            "message":  "File uploaded successfully",
            "filename": filename,
            "size":     file.Size,
        })
    })
}

WebSocket 支持 #

import (
    "github.com/gofiber/websocket/v2"
)

func setupWebSocket(app *fiber.App) {
    // WebSocket 升级中间件
    app.Use("/ws", func(c *fiber.Ctx) error {
        if websocket.IsWebSocketUpgrade(c) {
            c.Locals("allowed", true)
            return c.Next()
        }
        return fiber.ErrUpgradeRequired
    })

    // WebSocket 连接处理
    app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
        // 获取连接参数
        userID := c.Params("id")

        // 连接管理
        clients[userID] = c
        defer delete(clients, userID)

        // 发送欢迎消息
        c.WriteJSON(fiber.Map{
            "type":    "welcome",
            "message": "Connected successfully",
            "user_id": userID,
        })

        // 消息处理循环
        for {
            var msg map[string]interface{}
            if err := c.ReadJSON(&msg); err != nil {
                break
            }

            // 处理不同类型的消息
            switch msg["type"] {
            case "chat":
                broadcastMessage(msg)
            case "ping":
                c.WriteJSON(fiber.Map{"type": "pong"})
            default:
                c.WriteJSON(fiber.Map{
                    "type":  "error",
                    "error": "Unknown message type",
                })
            }
        }
    }))

    // 广播消息的 HTTP 接口
    app.Post("/broadcast", func(c *fiber.Ctx) error {
        var msg map[string]interface{}
        if err := c.BodyParser(&msg); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid message format",
            })
        }

        broadcastMessage(msg)
        return c.JSON(fiber.Map{
            "message": "Message broadcasted",
        })
    })
}

var clients = make(map[string]*websocket.Conn)

func broadcastMessage(msg map[string]interface{}) {
    for userID, conn := range clients {
        if err := conn.WriteJSON(msg); err != nil {
            // 连接已断开,清理
            delete(clients, userID)
        }
    }
}

完整示例应用 #

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/logger"
    "github.com/gofiber/fiber/v2/middleware/recover"
    "strconv"
    "time"
)

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

var users = []User{
    {ID: 1, Name: "John Doe", Email: "[email protected]", CreatedAt: time.Now(), UpdatedAt: time.Now()},
    {ID: 2, Name: "Jane Smith", Email: "[email protected]", CreatedAt: time.Now(), UpdatedAt: time.Now()},
}

func main() {
    // 创建 Fiber 应用
    app := fiber.New(fiber.Config{
        ErrorHandler: func(c *fiber.Ctx, err error) error {
            code := fiber.StatusInternalServerError
            if e, ok := err.(*fiber.Error); ok {
                code = e.Code
            }

            return c.Status(code).JSON(fiber.Map{
                "error": err.Error(),
            })
        },
    })

    // 中间件
    app.Use(recover.New())
    app.Use(logger.New())
    app.Use(cors.New())

    // 路由设置
    setupRoutes(app)

    // 启动服务器
    app.Listen(":3000")
}

func setupRoutes(app *fiber.App) {
    // 健康检查
    app.Get("/health", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "status": "ok",
            "time":   time.Now(),
        })
    })

    // API 路由组
    api := app.Group("/api/v1")

    // 用户相关路由
    api.Get("/users", getUsers)
    api.Get("/users/:id", getUser)
    api.Post("/users", createUser)
    api.Put("/users/:id", updateUser)
    api.Delete("/users/:id", deleteUser)
}

func getUsers(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{
        "data":  users,
        "total": len(users),
    })
}

func getUser(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }

    for _, user := range users {
        if user.ID == id {
            return c.JSON(user)
        }
    }

    return c.Status(404).JSON(fiber.Map{
        "error": "User not found",
    })
}

func createUser(c *fiber.Ctx) error {
    var user User
    if err := c.BodyParser(&user); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid request format",
        })
    }

    user.ID = len(users) + 1
    user.CreatedAt = time.Now()
    user.UpdatedAt = time.Now()
    users = append(users, user)

    return c.Status(201).JSON(user)
}

func updateUser(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }

    var updatedUser User
    if err := c.BodyParser(&updatedUser); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid request format",
        })
    }

    for i, user := range users {
        if user.ID == id {
            updatedUser.ID = id
            updatedUser.CreatedAt = user.CreatedAt
            updatedUser.UpdatedAt = time.Now()
            users[i] = updatedUser
            return c.JSON(updatedUser)
        }
    }

    return c.Status(404).JSON(fiber.Map{
        "error": "User not found",
    })
}

func deleteUser(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }

    for i, user := range users {
        if user.ID == id {
            users = append(users[:i], users[i+1:]...)
            return c.JSON(fiber.Map{
                "message": "User deleted successfully",
            })
        }
    }

    return c.Status(404).JSON(fiber.Map{
        "error": "User not found",
    })
}

通过本节的学习,你已经掌握了 Fiber 框架的核心特性和使用方法。Fiber 以其极高的性能和熟悉的 Express.js 风格 API,为 Go Web 开发提供了强大而高效的解决方案。在实际项目中,可以充分利用 Fiber 的高性能特性和丰富的中间件生态,构建快速、可靠的 Web 应用。