go-fiber-fast
go-fiber 主要定位为一个轻量级、高性能的 Web 框架,但其灵活性使得它可以通过与其他库的集成,构建出强大而多功能的应用程序,满足不同的业务需求,和gin一样轻量级别的路由,但是性能特别是极端性能比gin好一些,都可以通过整合其他sdk服务来达到效果,由于使用 fasthttp 作为 HTTP 引擎,使得 Fiber 的性能非常出色。(Fasthttp是Go最快的HTTP 引擎。旨在简化快速开发**,零内存分配,并考虑**性能。)
由于该框架再国外文档 纯英文 所以写下该笔记 日后复习 该框架的时候和gin还是很相似的,主要是通过封装的上下文来进行操作
官方文档
https://gofiber.io/ https://docs.fiber.org.cn/
官网提示
从Node.js转到Go的新 Go 开发者在开始构建 Web 应用程序或微服务之前需要经历一段学习曲线。Fiber 作为一个Web 框架,以****极简主义的理念创建,并遵循UNIX 方式,以便新 Go 开发者能够以热情和信任的态度快速进入 Go 世界。
Fiber 的灵感来自于互联网上最流行的 Web 框架 Express。我们将Express 的易用性与 Go 的****原始性能相结合。如果您曾经在 Node.js 中实现过 Web 应用程序(使用 Express 或类似框架),那么许多方法和原则对您来说会显得非常常见。
Fiber v3 目前处于测试阶段,正在积极开发中。虽然它提供了令人兴奋的新功能,但请注意,它可能不适合生产使用。我们建议在关键任务应用程序上使用最新的稳定版本 (v2.x)。如果您选择使用 v3,请为潜在的错误和重大更改做好准备。请务必查看官方文档和发行说明以获取更新并谨慎行事。祝您编码愉快!
所以该笔记主要讲解v2
限制
- 由于 Fiber 使用了 unsafe,该库可能并不总是与最新的 Go 版本兼容。Fiber v3 已使用 Go 版本 1.22 和 1.23 进行了测试。
- Fiber 与 net/http 接口不兼容。这意味着您将无法使用 gqlgen、go-swagger 或属于 net/http 生态系统的任何其他项目。
下面是笔者之前的笔记 javaer快速学习go-gin
https://blog.csdn.net/qq_55272229/article/details/141233160?spm=1001.2014.3001.5501
fiber官方的web性能测验结果图
下面代码gitee地址 https://gitee.com/hou-chengyi/go-fiber-fast.git
快速体验
新建目录 然后进入初始化一个go.mod文件
安装相关依赖
go get -u github.com/gofiber/fiber/v2
截至目前2024.8.29 官方推荐的推荐的是v2版本 v3目前还不稳定 官方git: https://github.com/gofiber/fiber 国外的框架需科学上网
快速启动新建 一个main文件
package mainimport "github.com/gofiber/fiber/v2"func main() {app := fiber.New()//绑定路由 和处理路由的处理器app.Get("/", func(c *fiber.Ctx) error {return c.SendString("Hello, World!")})app.Get("/hello", func(c *fiber.Ctx) error {return c.SendString("你好啊 路由绑定成功!")})err := app.Listen(":3000")if err != nil {return}
}
访问localhost:3000/hello
得到响应 ! 所以熟悉gin这个是不是入门超级快呢
路由绑定
基本路由
V2 和v3的不同是处理路由的中间传参使用的是结构体而不是指针
基本路由绑定 这些方法和和gin中的绑定案列都是一样的
在 Fiber 中,你可以使用 app.Get()
, app.Post()
, app.Put()
, app.Delete()
等方法来绑定不同 HTTP 方法的路由。
import ("fmt""github.com/gofiber/fiber/v2""log"
)func hello(c *fiber.Ctx) error {msg := fmt.Sprintf("✋ %s", c.Params("*"))return c.SendString(msg) // => ✋ register
}/*
**/
func main() {app := fiber.New()//普通绑定app.Get("/", hello)app.Get("/hello", func(c *fiber.Ctx) error {return c.SendString("Hello, World!")
})app.Post("/submit", func(c *fiber.Ctx) error {return c.SendString("Form Submitted!")
})log.Fatal(app.Listen(":3000"))
}
分组绑定
和gin还是很类似的
写一个路由controller 或者handler 一样的
// 模拟设备控制器
type Device struct {Id string `json:"id"`Name string `json:"name"`Number string `json:"number"`
}type DeviceController struct {
}func NewDeviceController() *DeviceController {return &DeviceController{}
}func (d *DeviceController) GetDevices(c *fiber.Ctx) error {return c.JSON(fiber.Map{"message": "查询成功","data": Device{Id: "1", Name: "设备1", Number: "123456"},})
}func (d *DeviceController) CreateDevice(c *fiber.Ctx) error {return c.JSON(fiber.Map{"message": "创建成功","data": "创建成功",})
}
路由注册中间件
//分组绑定device := app.Group("/device")device.Get("/info", deviceController.GetDevices)device.Post("/", deviceController.CreateDevice)
提供静态文件路由(v3版本)
//提供静态文件
app.Get("/*", static.New("./public"))
v2 采用
此时静态文件目录和/路由绑定在一起 访问ip:端口/文件地址 就可以直接访问文件数据
app.Static("/", "router/public")
重构注册路由
和Springboot这样的框架注册路由相比 gin和fib已经相当简洁了 但是还是可以再次进行重构来进行操作
1. 使用 Router 和 Controller
你可以将路由绑定逻辑和处理函数分离到不同的文件中,从而保持代码的整洁和可维护性。具体做法如下:
1.1. 创建 Controller
首先,创建控制器文件 device_controller.go
,并定义控制器的方法:
// controller/device_controller.go
package controllerimport "github.com/gofiber/fiber/v2"type DeviceController struct{}func NewDeviceController() *DeviceController {return &DeviceController{}
}func (dc *DeviceController) GetDevices(c *fiber.Ctx) error {// 处理获取设备的逻辑return c.SendString("Get Devices")
}func (dc *DeviceController) CreateDevice(c *fiber.Ctx) error {// 处理创建设备的逻辑return c.SendString("Create Device")
}
1.2. 创建路由配置文件
然后,在 routes.go
文件中,配置路由绑定逻辑:
// routes/routes.go
package routesimport ("github.com/gofiber/fiber/v2""yourapp/controller"
)func SetupRoutes(app *fiber.App) {deviceController := controller.NewDeviceController()device := app.Group("/device")device.Get("/info", deviceController.GetDevices)device.Post("/", deviceController.CreateDevice)// 可以添加更多的路由组和控制器
}
1.3. 主程序文件
最后,在主程序文件中调用路由配置函数:
// main.go
package mainimport ("github.com/gofiber/fiber/v2""yourapp/routes" // 替换为实际路径
)func main() {app := fiber.New()// 设置路由routes.SetupRoutes(app)// 启动服务器app.Listen(":3000")
}
2. 使用 Route Setup Function
你可以将路由绑定逻辑封装到一个函数中,以减少重复代码,并使主程序文件更加简洁:
// routes/device_routes.go
package routesimport ("github.com/gofiber/fiber/v2""yourapp/controller"
)func SetupDeviceRoutes(app *fiber.App) {deviceController := controller.NewDeviceController()device := app.Group("/device")device.Get("/info", deviceController.GetDevices)device.Post("/", deviceController.CreateDevice)
}
在 main.go
中调用这个函数:
// main.go
package mainimport ("github.com/gofiber/fiber/v2""yourapp/routes" // 替换为实际路径
)func main() {app := fiber.New()// 设置路由routes.SetupDeviceRoutes(app)// 启动服务器app.Listen(":3000")
}
3. 动态路由注册
如果你有很多控制器和路由,考虑动态注册路由。可以将路由和控制器信息存储在配置文件或映射中,动态创建路由:
// routes/dynamic_routes.go
package routesimport ("github.com/gofiber/fiber/v2""yourapp/controller"
)func SetupDynamicRoutes(app *fiber.App) {routes := map[string]func(c *fiber.Ctx) error{"/device/info": controller.NewDeviceController().GetDevices,"/device": controller.NewDeviceController().CreateDevice,// 可以添加更多的路由和控制器}device := app.Group("/device")for route, handler := range routes {device.Add(route, handler)}
}
中间件
(老实说这个框架的api大多数和gin都一模一样 甚至实现逻辑都差不多 说是参考了express 不同在于底层引擎 至于谁借鉴谁 难说)
和路由处理器一样 ,都是以fiber封装的上下文为参数 对请求进行处理 , Next api的调用决定了中间件的执行为前置还是后置 调用下一个中间件 控制权给下一个中间件 否则就会停止执行
func main() {app := fiber.New()// 全局中间件,注册顺序决定了执行顺序app.Use(func(c *fiber.Ctx) error {fmt.Println("🥇 再fiber app实列上注册的为全局中间件 并且为第一个注册 第一个执行")return c.Next() // 继续到下一个中间件})app.Use(func(c *fiber.Ctx) error {c.Next() // 调用下一个中间件 控制权给下一个中间件 否则就会停止执行fmt.Println(" 再fiber app实列上注册的为全局中间件 虽然是第二个注册的 但是放行上下文处理 其他中间件执行完毕才该我")return nil})// 匹配所有路由/api开头的接口 等效于分组app.Use("/api", func(c *fiber.Ctx) error {fmt.Println("🥈 第二个注册到api开头前缀的 第二个执行 等效注册到分组中间件")return c.Next() // 继续到下一个中间件或处理函数})// GET /api/listapp.Get("/api/list", func(c *fiber.Ctx) error {fmt.Println("🥉 到达接口")return c.SendString("Hello, World 👋!") // 返回响应})log.Fatal(app.Listen(":3000"))
}
由于路由处理的中间件本身就是中间件执行的终点 所以无需进行放行fiber上下文
上述中间件匹配代码 等效于分组注册
group.Use("/list", func(c *fiber.Ctx) error {fmt.Println("🥈 第二个注册到api开头前缀的 第二个执行 等效注册到分组中间件")return c.Next()})
中间注册顺序
func main() {app := fiber.New()app.Use(func(c *fiber.Ctx) error {fmt.Println("🥇 再fiber app实列上注册的为全局中间件 并且为第一个注册 第一个执行")return c.Next()})app.Use(func(c *fiber.Ctx) error {fmt.Println("🥇 再fiber app实列上注册的为全局中间件 第二个个注册 第二个执行")return c.Next()})app.Use(func(c *fiber.Ctx) error {c.Next()fmt.Println(" 后置中间件:1")return nil})app.Use(func(c *fiber.Ctx) error {c.Next()fmt.Println(" 后置中间件:2")return nil})group := app.Group("/api")// Match all routes starting with /apiapp.Use("/api", func(c *fiber.Ctx) error {fmt.Println("🥈 我是第第一个执行的分组中间件")return c.Next()})group.Use("/list", func(c *fiber.Ctx) error {fmt.Println("🥈我是第第二个执行的分组中间件 第二个注册到api开头前缀的 第二个执行 ")return c.Next()})// GET /api/listapp.Get("/api/list", func(c *fiber.Ctx) error {fmt.Println("🥉 到达接口")return c.SendString("Hello, World 👋!")})log.Fatal(app.Listen(":3000"))
}
类似java的过滤器链设计风格 先注册的先执行,后注册的后执行(前置中间件 ) 后置中间件则是后注册的先执行
框架中自带了很多中间件 用于处理web中的常见问题
上下文
和gin一样每次收到一个请求 都会被封装为携程隔离的上下文 那么同样可以在上下文中做处理 比如登录后存放当前用户信息
func main() {app := fiber.New()key := "userinfo"// 注册全局中间件app.Use(func(c *fiber.Ctx) error {fmt.Println("🥇 在 fiber app 实例上注册的为全局中间件,并且为第一个注册,第一个执行")var userinfo stringif bytes, err := json.Marshal(user{Name: "admin", Password: "123456"}); err != nil {fmt.Println(err.Error())} else {userinfo = string(bytes)}fmt.Println("存入个人数据: " + userinfo)c.Locals(key, userinfo) // 使用 Locals 存储数据return c.Next()})app.Use(func(c *fiber.Ctx) error {c.Next()get := c.Locals(key).(string) // 使用 Locals 读取数据fmt.Println("读取到当前用户操作信息: " + get)return nil})// Match all routes starting with /apiapp.Use("/api", func(c *fiber.Ctx) error {get := c.Locals(key).(string) // 使用 Locals 读取数据fmt.Println("分组中间件读取到当前用户操作信息: " + get)return c.Next()})// GET /api/listapp.Get("/api/list", func(c *fiber.Ctx) error {fmt.Println("🥉 到达接口")get := c.Locals(key).(string) // 使用 Locals 读取数据fmt.Println("接口读取到用户信息: " + get)u := new(user)json.Unmarshal([]byte(get), u)return c.SendString(fmt.Sprintf("Hello, World 👋! %s", u.Name))})log.Fatal(app.Listen(":3000"))
}
接收请求
fiber中对各个请求参数的接收
在 GoFiber 中,处理不同类型的请求参数(如路径参数、查询参数、请求体参数)非常常见。以下是如何在 GoFiber 中接收和处理这些参数的示例。
1. 路径参数(Route Parameters)
路径参数是 URL 中的一部分,通常用于标识资源。例如 /api/user/:id
中的 :id
。
app.Get("/api/user/:id", func(c *fiber.Ctx) error {id := c.Params("id") // 获取路径参数return c.SendString("User ID: " + id)
})
2. 查询参数(Query Parameters)
查询参数是 URL 中以 ?
开头的部分,通常用于传递非路径的附加数据。例如 /api/search?name=John&age=30
。
app.Get("/api/search", func(c *fiber.Ctx) error {name := c.Query("name") // 获取查询参数age := c.Query("age") // 获取查询参数return c.SendString("Name: " + name + ", Age: " + age)
})
3. 请求体参数(Body Parameters)
请求体参数用于发送更复杂的数据结构,通常通过 POST、PUT 等方法发送,支持不同的格式如 JSON、XML、表单数据等。
3.1 JSON
处理 JSON 格式的请求体:
type User struct {Name string `json:"name"`Age int `json:"age"`
}app.Post("/api/user", func(c *fiber.Ctx) error {user := new(User)if err := c.BodyParser(user); err != nil {return c.Status(fiber.StatusBadRequest).SendString(err.Error())}return c.JSON(user)
})
3.2 表单数据(Form Data)
处理 application/x-www-form-urlencoded
或 multipart/form-data
格式的表单数据:
app.Post("/api/form", func(c *fiber.Ctx) error {name := c.FormValue("name") // 获取表单数据参数age := c.FormValue("age")return c.SendString("Name: " + name + ", Age: " + age)
})
3.3 原始文本(Plain Text)
处理纯文本格式的请求体:
app.Post("/api/text", func(c *fiber.Ctx) error {body := c.Body() // 获取原始文本内容return c.SendString("Received text: " + string(body))
})
4. Headers(请求头)
获取请求头信息:
app.Get("/api/header", func(c *fiber.Ctx) error {contentType := c.Get("Content-Type") // 获取请求头参数return c.SendString("Content-Type: " + contentType)
})
5. 上传文件(File Uploads)
处理文件上传:
app.Post("/api/upload", func(c *fiber.Ctx) error {file, err := c.FormFile("file") // 获取上传的文件if err != nil {return c.Status(fiber.StatusBadRequest).SendString(err.Error())}return c.SaveFile(file, "./uploads/"+file.Filename) // 保存文件
})//多文件上传-- 获取所有上传的文件// form, err := c.MultipartForm()//if err != nil {// return c.Status(fiber.StatusBadRequest).SendString("Failed to get multipart form: " + err.Error())//}
上传多个文件
- API 路径:
/upload
- 请求方法:POST
- 请求:使用工具(如 Postman)上传多个文件到
http://localhost:3000/upload
- 处理函数:
app.Post("/upload", func(c *fiber.Ctx) error {form, err := c.MultipartForm()if err != nil {return err}files := form.File["files"]for _, file := range files {err := c.SaveFile(file, "./uploads/"+file.Filename)if err != nil {return err}}return c.JSON(fiber.Map{"status": "success","files": len(files),})
})
6. Cookies
获取和设置 Cookies:
app.Get("/api/set-cookie", func(c *fiber.Ctx) error {c.Cookie(&fiber.Cookie{Name: "session_id",Value: "123456",})return c.SendString("Cookie set")
})app.Get("/api/get-cookie", func(c *fiber.Ctx) error {sessionID := c.Cookies("session_id") // 获取 cookiereturn c.SendString("Session ID: " + sessionID)
})
session
对于session fiber需要安装对应的中间件进行操作
// 初始化 session 存储store := session.New()// 使用 session 中间件app.Use(func(c *fiber.Ctx) error {// 获取会话sess, err := store.Get(c)if err != nil {return err}// 在中间件中设置或操作会话数据if sess.Get("name") == nil {// 如果 "name" 不存在,则设置一个默认值sess.Set("name", "default_user")} else {// 如果 "name" 存在,更新或读取其值name := sess.Get("name").(string)log.Println("Current user:", name)}// 保存会话if err := sess.Save(); err != nil {return err}// 继续处理下一个中间件或路由return c.Next()})// 访问会话数据的路由示例app.Get("/profile", func(c *fiber.Ctx) error {// 从会话中获取数据sess, err := store.Get(c)if err != nil {return err}name := sess.Get("name").(string)return c.SendString("Hello, " + name)})// 修改会话数据的路由示例app.Post("/login", func(c *fiber.Ctx) error {sess, err := store.Get(c)if err != nil {return err}// 从请求中获取用户名并设置到会话中username := c.FormValue("username")sess.Set("name", username)// 保存会话if err := sess.Save(); err != nil {return err}return c.SendString("Logged in as: " + username)})log.Fatal(app.Listen(":3000"))
}
测试代码
func main() {app := fiber.New(fiber.Config{BodyLimit: 100 * 1024 * 1024, // 100 MB})// 初始化 session 存储store := session.New()// 使用 session 中间件app.Use(func(c *fiber.Ctx) error {// 获取会话sess, err := store.Get(c)if err != nil {return err}// 在中间件中设置或操作会话数据if sess.Get("name") == nil {// 如果 "name" 不存在,则设置一个默认值sess.Set("name", "default_user")} else {// 如果 "name" 存在,更新或读取其值name := sess.Get("name").(string)log.Info("Current user:", name)}// 保存会话if err := sess.Save(); err != nil {return err}// 继续处理下一个中间件或路由return c.Next()})// 访问会话数据的路由示例app.Get("/profile", func(c *fiber.Ctx) error {// 从会话中获取数据sess, err := store.Get(c)if err != nil {return err}name := sess.Get("name").(string)return c.SendString("Hello, " + name)})type User struct {Name string `json:"name"`Age int `json:"age"`}// GET /api/listapp.Get("/", func(c *fiber.Ctx) error {return c.SendString(fmt.Sprintf("Hello, World 👋! %s", "本案列按时如何接收请求的各个方式传值"))})app.Get("/api/user/:id", func(c *fiber.Ctx) error {id := c.Params("id") // 获取路径参数return c.SendString("收到用户id: " + id)})app.Get("/user/list", func(c *fiber.Ctx) error {page := c.Query("page") // 获取查询参数size := c.Query("size") // 获取查询参数return c.SendString("分页页码: " + page + ", 分页大小: " + size)})//新增用户app.Post("/api/user", func(c *fiber.Ctx) error {name := c.FormValue("name") // 获取表单数据参数age := c.FormValue("age")return c.SendString("新增用户: " + name + ", 年龄: " + age)})app.Post("/api/jsonuser", func(c *fiber.Ctx) error {user := new(User)if err := c.BodyParser(user); err != nil {return c.Status(fiber.StatusBadRequest).SendString(err.Error())}return c.JSON(fiber.Map{"message": "添加用户成功","data": user,})})app.Post("/api/text", func(c *fiber.Ctx) error {body := c.Body() // 获取原始文本内容return c.SendString("接收文本内容: " + string(body))})/**上传文件*/app.Post("/api/upload", func(c *fiber.Ctx) error {file, err := c.FormFile("file") // 获取上传的文件if err != nil {return c.Status(fiber.StatusBadRequest).SendString(err.Error())}// 在应用启动时检查并创建上传目录if _, err := os.Stat("./uploads"); os.IsNotExist(err) {err := os.Mkdir("./uploads", os.ModePerm)if err != nil {log.Fatalf("Failed to create uploads directory: %v", err)}}return c.SaveFile(file, "./uploads/"+file.Filename) // 保存文件})app.Get("/api/set-cookie", func(c *fiber.Ctx) error {c.Cookie(&fiber.Cookie{Name: "session_id",Value: "我是手动保存到游览器的cookie数值",})return c.SendString("Cookie set")})app.Get("/api/get-cookie", func(c *fiber.Ctx) error {sessionID := c.Cookies("session_id") // 获取 cookiereturn c.SendString("Session ID: " + sessionID)})log.Fatal(app.Listen(":3000"))
}
响应结果
fiber 也对常见的web响应结果 进行了封装
1. 文本
- API 路径:
/text
- 请求方法:GET
- 请求:
curl http://localhost:3000/text
- 处理函数:
app.Get("/text", func(c *fiber.Ctx) error {return c.SendString("Hello, World 👋!")
})
2. **JSON **
- API 路径:
/json
- 请求方法:GET
- 请求:
curl http://localhost:3000/json
- 处理函数:
app.Get("/json", func(c *fiber.Ctx) error {response := fiber.Map{"message": "Hello, World 👋!","status": "success",}return c.JSON(response)
})
3. 文件下载
- API 路径:
/download
- 请求方法:GET
- 请求:
curl http://localhost:3000/download
- 处理函数:
app.Get("/download", func(c *fiber.Ctx) error {filePath := "./uploads/sample.pdf"return c.Download(filePath)
})
4. 二进制文件响应
- API 路径:
/file
- 请求方法:GET
- 请求:
curl http://localhost:3000/file
- 处理函数:
app.Get("/file", func(c *fiber.Ctx) error {filePath := "./uploads/sample.pdf"return c.SendFile(filePath)
})
5. 数据流响应
- API 路径:
/stream
- 请求方法:GET
- 请求:
curl http://localhost:3000/stream
- 处理函数:
app.Get("/stream", func(c *fiber.Ctx) error {data := []byte("你好 世界 👋!...")c.Set("Content-Type", "text/event-stream")return c.SendStream(bytes.NewReader(data), len(data))})
6. WebSocket 通信
- API 路径:
/ws
- 请求方法:WebSocket
- 请求:使用 WebSocket 客户端连接到
ws://localhost:3000/ws
- 处理函数:
app.Get("/ws", websocket.New(func(c *websocket.Conn) {for {mt, message, err := c.ReadMessage()if err != nil {log.Println("read:", err)break}log.Printf("recv: %s", message)err = c.WriteMessage(mt, message)if err != nil {log.Println("write:", err)break}}
}))
6.1socket的高级用法
比如实现广播 会话管理
package mainimport ("log""sync""github.com/gofiber/fiber/v2""github.com/gofiber/websocket/v2"
)//会话工具类代码
var (// 用于存储 WebSocket 会话的线程安全映射 类似java的concurrentmapsessions sync.Map
)// 存储会话
func storeSession(key string, conn *websocket.Conn) {sessions.Store(key, conn)
}// 获取会话
func getSession(key string) (*websocket.Conn, bool) {conn, ok := sessions.Load(key)if ok {return conn.(*websocket.Conn), true}return nil, false
}// 删除会话
func deleteSession(key string) {sessions.Delete(key)
}//服务端代码
func main() {app := fiber.New()app.Get("/ws/:userID", websocket.New(func(c *websocket.Conn) {userID := c.Params("userID")log.Printf("User %s connected", userID)// 存储连接到会话映射中storeSession(userID, c)// 处理消息for {mt, message, err := c.ReadMessage()if err != nil {log.Println("Read error:", err)break}log.Printf("Received from %s: %s", userID, message)// 回显消息if err := c.WriteMessage(mt, message); err != nil {log.Println("Write error:", err)break}}// 连接关闭log.Printf("User %s disconnected", userID)deleteSession(userID)}))log.Fatal(app.Listen(":3000"))
}//根据socket收到的消息发给具体用户
func sendMessageToUser(userID string, message string) {conn, ok := getSession(userID)if ok {if err := conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {log.Println("Send message error:", err)}} else {log.Println("No session found for user:", userID)}
}
测试代码
func main() {app := fiber.New()// 文本响应app.Get("/text", func(c *fiber.Ctx) error {return c.SendString("Hello, World 👋!")})// JSON 响应app.Get("/json", func(c *fiber.Ctx) error {response := fiber.Map{"message": "你好 世界 👋!","status": "success",}return c.JSON(response)})// 文件下载app.Get("/download", func(c *fiber.Ctx) error {filePath := "./uploads/4.jpg"return c.Download(filePath)})// 二进制文件响应app.Get("/file", func(c *fiber.Ctx) error {filePath := "./uploads/4.jpg"return c.SendFile(filePath)})// Stream 数据// Stream 数据app.Get("/stream", func(c *fiber.Ctx) error {data := []byte("你好 世界 👋!...")c.Set("Content-Type", "text/event-stream")return c.SendStream(bytes.NewReader(data), len(data))})// WebSocket 通信app.Get("/ws", websocket.New(func(c *websocket.Conn) {for {// 读取消息mt, message, err := c.ReadMessage()if err != nil {log.Println("读取消息错误:", err)break}log.Printf("收到消息: %s", message)/**第一个参数的数字代表websocket.TextMessage (1): 表示消息是文本类型。websocket.BinaryMessage (2): 表示消息是二进制类型。websocket.CloseMessage (8): 表示关闭消息。 使用这个状态码后关闭通道不会发送消息websocket.PingMessage (9): 表示Ping消息。websocket.PongMessage (10): 表示Pong消息。*/// 响应消息err = c.WriteMessage(mt, message)if err != nil {log.Println("写入与消息错误:", err)break}}}))log.Fatal(app.Listen(":3000"))
}
自定义中间件
到目前位置一个web框架的功能已经能基本实现了 对于 gin和fib强大的是轻量的同时还是可以使用中间链来实现很多复杂的功能
统一错误
// 自定义错误处理中间件app.Use(func(c *fiber.Ctx) error {// 先处理请求if err := c.Next(); err != nil {// 检查错误类型并返回相应的状态码和消息var appErr *AppErrorif ok := errorAs(err, &appErr); ok {// 自定义错误类型return c.Status(appErr.StatusCode).JSON(fiber.Map{"error": appErr.Message,})}// 默认处理return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "服务器错误",})}return nil})// 示例路由app.Get("/error", func(c *fiber.Ctx) error {return &AppError{StatusCode: fiber.StatusBadRequest,Message: "这是一个自定义的错误",}})
限流中间件
package mainimport ("log""sync""time""github.com/gofiber/fiber/v2"
)var (requestCounts = sync.Map{} // 存储每个 IP 的请求次数和时间blacklist = sync.Map{} // 存储被拉黑的 IP 及其拉黑过期时间maxRequests = 100 // 最大请求次数window = time.Minute // 时间窗口这里设置的1分种blacklistTime = 10 * time.Minute // 拉黑时间
)func limitRate(c *fiber.Ctx) error {clientIP := c.IP()// 检查是否在黑名单中if blacklistTime, exists := blacklist.Load(clientIP); exists {if time.Now().Before(blacklistTime.(time.Time)) {//如果黑名单种return c.Status(fiber.StatusTooManyRequests).SendString("请求过多,请稍后再试")}// 从黑名单中移除blacklist.Delete(clientIP)}// 限流逻辑if value, exists := requestCounts.Load(clientIP); exists {data := value.(struct {Count intLastTime time.Time})// 判断时间窗口是否过期if time.Since(data.LastTime) > window {// 重置请求次数requestCounts.Store(clientIP, struct {Count intLastTime time.Time}{Count: 1,LastTime: time.Now(),})return c.Next()}// 检查请求次数if data.Count >= maxRequests {// 添加到黑名单blacklist.Store(clientIP, time.Now().Add(blacklistTime))return c.Status(fiber.StatusTooManyRequests).SendString("请求过多,请稍后再试")}// 更新请求次数requestCounts.Store(clientIP, struct {Count intLastTime time.Time}{Count: data.Count + 1,LastTime: data.LastTime,})} else {// 添加新的 IP 记录requestCounts.Store(clientIP, struct {Count intLastTime time.Time}{Count: 1,LastTime: time.Now(),})}return c.Next()
}func main() {app := fiber.New()// 使用限流中间件app.Use(limitRate)// 示例路由app.Get("/", func(c *fiber.Ctx) error {return c.SendString("Hello, World!")})log.Fatal(app.Listen(":3000"))
}
.限流策略的目的是限制每个 IP 地址在指定时间窗口内的请求次数,从而防止滥用和保护服务器资源。具体来说,代码实现了基于时间窗口的限流策略,其核心思路如下:
限流策略说明
- 时间窗口:
- 限流策略使用了一个时间窗口 (
window
),在此时间窗口内,客户端 IP 的请求次数会被记录和限制。例如,window
设置为 1 分钟 (time.Minute
),表示每个 IP 地址在 1 分钟内最多可以发起maxRequests
次请求。
- 限流策略使用了一个时间窗口 (
- 最大请求次数:
maxRequests
设置了每个 IP 地址在时间窗口内允许的最大请求次数。例如,maxRequests
设置为 100,意味着每个 IP 地址在 1 分钟内最多可以发起 100 次请求。如果超过这个限制,该 IP 地址将被添加到黑名单中。
- 黑名单机制:
- 如果某个 IP 地址在时间窗口内的请求次数超过了
maxRequests
,它会被添加到黑名单中,并在一段时间内(blacklistTime
,例如 10 分钟)被禁止继续发送请求。在此期间内的请求会被拒绝,并返回“请求过多”的错误信息。
- 如果某个 IP 地址在时间窗口内的请求次数超过了
100个请求依旧可以
101个即开始收到429状态码
钩子函数
fiber种对于路由的各个周期都有对应的钩子函数 主要针对2.30版本
go get github.com/gofiber/fiber/v2@v2.30.0
测试i代码
可以对项目的路由等生命周期进行输出查看
func main() {app := fiber.New(fiber.Config{DisableStartupMessage: true,})// 在路由注册时触发app.Hooks().OnRoute(func(route fiber.Route) error {log.Printf("路由组成: %s", route.Path)return nil})// 当路由命名时触发app.Hooks().OnName(func(r fiber.Route) error {fmt.Print("路由命名: " + r.Name + ", ")return nil})group := app.Group("/api")group.Get("/", func(c *fiber.Ctx) error {return c.SendString("访问成功")})// 在路由组创建时触发app.Hooks().OnGroup(func(group fiber.Group) error {log.Printf("组创建: %s", group.Prefix)return nil})// 当路由组命名时触发app.Hooks().OnGroupName(func(group fiber.Group) error {log.Printf("Group name: %s", group.Prefix)return nil})// 在应用启动并开始监听时触发addr := ":3000"// 示例路由app.Get("/", func(c *fiber.Ctx) error {return c.SendString("Hello, World!")})// 启动服务器go func() {if err := app.Listen(addr); err != nil {log.Fatalf("Failed to start server: %v", err)}}()// 模拟应用运行time.Sleep(10 * time.Second)// 关闭服务器if err := app.Shutdown(); err != nil {log.Fatalf("Failed to shut down server: %v", err)}
}
数据校验
官方的实现起来比较麻烦
官方演示代码 :数据校验必须query中的数据名字是5-20 年龄是12-18
package mainimport ("fmt""log""strings""github.com/go-playground/validator/v10""github.com/gofiber/fiber/v2"
)// 定义用户结构体
type (User struct {Name string `validate:"required,min=5,max=20"` // Name字段是必填项,长度最少5个字符,最多20个字符Age int `validate:"required,teenager"` // Age字段是必填项,并且需要符合自定义的'teenager'标签}// 错误响应结构体ErrorResponse struct {Error boolFailedField stringTag stringValue interface{}}// 自定义的校验器结构体XValidator struct {validator *validator.Validate}// 全局错误处理响应结构体GlobalErrorHandlerResp struct {Success bool `json:"success"`Message string `json:"message"`}
)// 实例化一个全局的校验器
var validate = validator.New()// 校验方法,接受一个结构体并返回错误信息
func (v XValidator) Validate(data interface{}) []ErrorResponse {validationErrors := []ErrorResponse{}// 对传入的结构体进行校验errs := validate.Struct(data)if errs != nil {// 如果有校验错误,遍历错误信息for _, err := range errs.(validator.ValidationErrors) {// 创建一个错误响应var elem ErrorResponseelem.FailedField = err.Field() // 获取错误的字段名elem.Tag = err.Tag() // 获取违反的标签规则elem.Value = err.Value() // 获取该字段的实际值elem.Error = true// 将错误响应添加到错误列表中validationErrors = append(validationErrors, elem)}}// 返回所有校验错误信息return validationErrors
}func main() {// 创建一个自定义校验器myValidator := &XValidator{validator: validate,}// 创建 Fiber 实例,并配置全局错误处理app := fiber.New(fiber.Config{ErrorHandler: func(c *fiber.Ctx, err error) error {// 返回 JSON 格式的错误信息return c.Status(fiber.StatusBadRequest).JSON(GlobalErrorHandlerResp{Success: false,Message: err.Error(),})},})// 注册自定义的校验规则'teenager'myValidator.validator.RegisterValidation("teenager", func(fl validator.FieldLevel) bool {// 自定义规则:年龄必须在12到18岁之间return fl.Field().Int() >= 12 && fl.Field().Int() <= 18})// 定义路由app.Get("/", func(c *fiber.Ctx) error {// 创建用户对象,并从请求中获取name和age参数user := &User{Name: c.Query("name"),Age: c.QueryInt("age"),}// 校验用户输入if errs := myValidator.Validate(user); len(errs) > 0 && errs[0].Error {// 如果校验有错误,收集所有错误信息errMsgs := make([]string, 0)for _, err := range errs {errMsgs = append(errMsgs, fmt.Sprintf("[%s]: '%v' | 需要符合规则 '%s'",err.FailedField,err.Value,err.Tag,))}// 返回错误信息return &fiber.Error{Code: fiber.ErrBadRequest.Code,Message: strings.Join(errMsgs, " 和 "),}}// 如果校验通过,返回成功信息return c.SendString("验证成功!")})// 启动服务器,监听3000端口log.Fatal(app.Listen(":3000"))
}
个人更喜欢的是依赖中间件,校验规则注册到validate后 使用对应路由的前置中间件 可以定义返回消息
package mainimport ("fmt""log""strings""github.com/go-playground/validator/v10""github.com/gofiber/fiber/v2"
)// 定义用户结构体
type User struct {Name string `validate:"required" json:"name"` // Name字段是必填项,长度最少5个字符,最多20个字符Age int `validate:"required,intRange" json:"age"` // Age字段使用自定义的intRange标签校验Score int `validate:"required,intRange" json:"score"` // Score字段使用同一个自定义的intRange标签校验Address string `validate:"required,home" json:"address"` // Score字段使用同一个自定义的intRange标签校验
}// 实例化全局的校验器
var validate = validator.New()func init() {// 注册自定义的校验规则'intRange'validate.RegisterValidation("intRange", func(fl validator.FieldLevel) bool {value := fl.Field().Int()switch fl.FieldName() {case "Age":return value >= 12 && value <= 18case "Score":return value >= 0 && value <= 100default:return false}})validate.RegisterValidation("home", func(fl validator.FieldLevel) bool {value := fl.Field().String()switch fl.FieldName() {case "Adress":return strings.Contains(value, "重庆市")default:return false}})}// 创建数据校验中间件
func validateMiddleware(schema interface{}) fiber.Handler {return func(c *fiber.Ctx) error {// 将请求体解析到结构体if err := c.BodyParser(schema); err != nil {return c.Status(fiber.StatusBadRequest).SendString("请求数据格式错误")}// 进行校验err := validate.Struct(schema)if err != nil {//组装校验信息并返回var errorMessages []string//对已经注册校验规则进行判断for _, err := range err.(validator.ValidationErrors) {errorMessages = append(errorMessages, fmt.Sprintf("字段 '%s' 无效: %v (不满足规则: %s)",err.Field(),err.Value(),err.Tag(),))}return c.Status(fiber.StatusBadRequest).SendString(strings.Join(errorMessages, "; "))}// 校验通过,继续处理请求return c.Next()}
}func main() {app := fiber.New()// 定义路由,并应用校验中间件app.Post("/user", validateMiddleware(&User{}), func(c *fiber.Ctx) error {// 处理逻辑return c.SendString("用户数据有效")})// 启动服务器,监听3000端口log.Fatal(app.Listen(":3000"))
}
性能优化
即使fiber的性能已经很强了 官方文档依旧给出了性能优化 以及可以配置进行优化
官方:自 Fiber v2.32.0 起,我们使用 encoding/json 作为默认 json 库,因为它稳定且具有可生产性。但是,与第三方库相比,标准库有点慢。如果你对 encoding/json 的性能不满意,我们建议你使用这些库
- goccy/go-json
- bytedance/sonic
- segmentio/encoding
- mailru/easyjson
- minio/simdjson-go
- wI2L/jettison
优化性能配置
package mainimport (/**自 Fiber v2.32.0 起,我们使用 encoding/json 作为默认 json 库,因为它稳定且具有可生产性。但是,与第三方库相比,标准库有点慢。如果你对 encoding/json 的性能不满意,我们建议你使用这些库*/"github.com/goccy/go-json""github.com/gofiber/fiber/v2""log""time""github.com/gofiber/fiber/v2/middleware/compress""github.com/gofiber/fiber/v2/middleware/cors""github.com/gofiber/fiber/v2/middleware/limiter""github.com/gofiber/fiber/v2/middleware/logger""github.com/gofiber/fiber/v2/middleware/recover"
)func main() {// Fiber 配置app := fiber.New(fiber.Config{JSONEncoder: json.Marshal,JSONDecoder: json.Unmarshal,Prefork: true, // 开启 Prefork 模式,利用多核 CPU 提升性能IdleTimeout: 5 * time.Second, // 连接空闲超时时间,防止资源被长时间占用DisableStartupMessage: true, // 禁用启动消息,减少启动开销})// 中间件配置app.Use(recover.New()) // 捕获并恢复 panic,防止程序崩溃app.Use(logger.New()) // 记录请求日志,方便调试和监控app.Use(compress.New(compress.Config{Level: compress.LevelBestSpeed})) // 启用压缩中间件,减少响应体积// CORS 配置app.Use(cors.New(cors.Config{AllowOrigins: "*", // 设置跨域}))// 请求速率限制,防止恶意攻击 这里只是简单哪处理 还可以黑名单拉黑app.Use(limiter.New(limiter.Config{Max: 100, // 每分钟最多 100 次请求Expiration: 1 * time.Minute,}))// 示例路由app.Get("/", func(c *fiber.Ctx) error {return c.SendString("Hello, Fiber!")})// 启动应用log.Fatal(app.Listen(":3000"))
}
版本 api差异
新版fiber主要适配当前go版本1.22,1.23
以下是 go-fiber
v2 和 v3 中主要的 API 改动列表:
1. 中间件
-
v2: 使用指针传递上下文
app.Use(func(c *fiber.Ctx) error {return c.Next() })
-
v3: 使用结构体传递上下文
app.Use(func(c fiber.Ctx) error {return c.Next() })
2. 路由
-
v2: 路由定义
app.Get("/path", handler)
-
v3: 路由定义基本相同,但可能有改进或新增参数支持
3. 请求上下文
-
v2: 上下文方法如
Params
,Query
,Body
使用方式id := c.Params("id")
-
v3: 上下文方法基本相同,但可能有语法或行为上的小调整具体看文档
4. 响应处理
-
v2: 使用
SendString
,JSON
,SendFile
等方法c.SendString("Hello")
-
v3: 方法使用基本相同,但可能有增强功能或新的方法
5. 错误处理
-
v2: 错误处理
app.Use(func(c *fiber.Ctx) error {if err := someOperation(); err != nil {return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")}return c.Next() })
-
v3: 错误处理机制基本相同,但可能有改进
6. 配置
-
v2: 配置项传递
app := fiber.New(fiber.Config{// 配置项 })
-
v3: 配置项传递方式类似,但可能有新增或移除的配置项
7. 静态文件
-
v2: 使用
app.Static()
app.Static("/", "./public")
-
v3: 使用
static.New()
,功能类似但方法调用方式有所变化app.Get("/*", static.New("./public"))
8. 插件和中间件
-
v2: 插件和中间件注册方式
app.Use("/path", someMiddleware)
-
v3: 插件和中间件注册可能有变化,新增功能或改进
9. 其他
- v2: 其他功能如
SendFile
,Redirect
,SendStatus
等 - v3: 功能大体相同,可能有新的 API 或功能改进