超详细带你学习go高性能web框架----fiber

2024-08-30 16:12

本文主要是介绍超详细带你学习go高性能web框架----fiber,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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-urlencodedmultipart/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 地址在指定时间窗口内的请求次数,从而防止滥用和保护服务器资源。具体来说,代码实现了基于时间窗口的限流策略,其核心思路如下:

限流策略说明

  1. 时间窗口
    • 限流策略使用了一个时间窗口 (window),在此时间窗口内,客户端 IP 的请求次数会被记录和限制。例如,window 设置为 1 分钟 (time.Minute),表示每个 IP 地址在 1 分钟内最多可以发起 maxRequests 次请求。
  2. 最大请求次数
    • maxRequests 设置了每个 IP 地址在时间窗口内允许的最大请求次数。例如,maxRequests 设置为 100,意味着每个 IP 地址在 1 分钟内最多可以发起 100 次请求。如果超过这个限制,该 IP 地址将被添加到黑名单中。
  3. 黑名单机制
    • 如果某个 IP 地址在时间窗口内的请求次数超过了 maxRequests,它会被添加到黑名单中,并在一段时间内(blacklistTime,例如 10 分钟)被禁止继续发送请求。在此期间内的请求会被拒绝,并返回“请求过多”的错误信息。

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 或功能改进

这篇关于超详细带你学习go高性能web框架----fiber的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121303

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

Java Web指的是什么

Java Web指的是使用Java技术进行Web开发的一种方式。Java在Web开发领域有着广泛的应用,主要通过Java EE(Enterprise Edition)平台来实现。  主要特点和技术包括: 1. Servlets和JSP:     Servlets 是Java编写的服务器端程序,用于处理客户端请求和生成动态网页内容。     JSP(JavaServer Pages)

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识