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 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.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 controller
import "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 routes
import (
"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 main
import (
"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 routes
import (
"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 main
import (
"github.com/gofiber/fiber/v2"
"yourapp/routes" // 替换为实际路径
)
func main() {
app := fiber.New()
// 设置路由
routes.SetupDeviceRoutes(app)
// 启动服务器
app.Listen(":3000")
}
3. 动态路由注册
如果你有很多控制器和路由,考虑动态注册路由。可以将路由和控制器信息存储在配置文件或映射中,动态创建路由:
// routes/dynamic_routes.go
package routes
import (
"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/list
app.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 /api
app.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/list
app.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 string
if 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 /api
app.Use("/api", func(c *fiber.Ctx) error {
get := c.Locals(key).(string) // 使用 Locals 读取数据
fmt.Println("分组中间件读取到当前用户操作信息: " + get)
return c.Next()
})
// GET /api/list
app.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") // 获取 cookie
return 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/list
app.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") // 获取 cookie
return 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 main
import (
"log"
"sync"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
//会话工具类代码
var (
// 用于存储 WebSocket 会话的线程安全映射 类似java的concurrentmap
sessions 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 *AppError
if 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 main
import (
"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 int
LastTime time.Time
})
// 判断时间窗口是否过期
if time.Since(data.LastTime) > window {
// 重置请求次数
requestCounts.Store(clientIP, struct {
Count int
LastTime 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 int
LastTime time.Time
}{
Count: data.Count + 1,
LastTime: data.LastTime,
})
} else {
// 添加新的 IP 记录
requestCounts.Store(clientIP, struct {
Count int
LastTime 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 main
import (
"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 bool
FailedField string
Tag string
Value 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 ErrorResponse
elem.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 main
import (
"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 <= 18
case "Score":
return value >= 0 && value <= 100
default:
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 的性能不满意,我们建议你使用这些库
优化性能配置
package main
import (
/**
自 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 或功能改进