Gin
框架是一个用 Go 语言编写的高性能 Web 框架,以其速度和简洁性著称。它由一个轻量级的 HTTP 路由器和一个中间件架构组成,能够处理大型流量并简化开发者的工作。Gin
的主要特点包括内置的路由组、简洁的 API 设计、强大的错误处理机制、支持多种格式的请求绑定和验证,以及内置的日志记录功能。由于其性能优越和易于使用,Gin
广泛应用于构建RESTful API
和 Web 服务。其设计理念是尽可能减少繁琐的配置和代码,让开发者专注于业务逻辑,实现快速开发和部署。
PS:据部分资料显示,HTTP
路由的优化,速度提升了 10 倍以上。但我是查到的主流资料,性能提升没这么夸张。对比我知道的一些框架,QPS 提升还可以,大多数都是小于 3 倍的,但是内存消耗非常小,内存分配和分配时间非常小。主要原因也是因为 gin
采用了大量的 sync.Pool
优化,这一点跟 fasthttp
一样的逻辑。所以池化技术是一个性能优化的大杀器。
下面我们来开始 gin
框架的实践之旅。
依赖和安装
- Go 语言环境
- 安装
gin
:go get -u github.com/gin-gonic/gin - 开始撸代码
这是一个例子
首先我们先看一个最简单的例子:
package main
import (
"github.com/gin-gonic/gin"
"net/http")
func main() {
//创建一个默认的路由引擎,默认使用了Logger和Recovery中间件
r := gin.Default()
//注册一个路由和处理函数
r.GET("/", func(c *gin.Context) {
//返回一个字符串,状态码是200
c.String(http.StatusOK, "Hello FunTester")
})
//监听端口,默认是8080,这里一般不设置IP,原因是在服务器上可能有多个IP,如果设置了IP,就只能监听这个IP
r.Run(":8000")
}
请求与响应
上面的每行代码的功能都已经写好了注释,是不是非常简单,其中 GET
方法的参数类型是: handlers ...HandlerFunc
,而 HandlerFunc
的定义是 type HandlerFunc func(*Context)
,其中 gin.Context
是 Gin
框架中最核心的结构之一,它提供了上下文环境,供处理 HTTP 请求和响应。gin.Context
包含许多有用的方法和属性,使开发者能够轻松访问请求数据、设置响应数据、处理错误以及在中间件和处理器之间传递信息。
下面是 gin.Context
构造方法:
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
params *Params
skippedNodes *[]skippedNode
// This mutex protects Keys map.
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]any
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache caches the query result from c.Request.URL.Query().
queryCache url.Values
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http.SameSite
}
相信看到这里,你一定若有所悟了吧。gin
框架用来处理请求响应都是靠 gin.Context
而请求和响应都包含在 gin.Context
当中。
gin
框架的请求方法是通过方法名直接设置的,例如上面例子中的 r.GET
就表示这个接口注册了 GET
方法的调用。下面是 gin
支持的调用方法:
- GET:用于从服务器获取资源。
- POST:用于向服务器提交数据,通常用于创建资源。
- PUT:用于更新服务器上的资源。
- DELETE:用于删除服务器上的资源。
- PATCH:用于部分更新服务器上的资源。
- HEAD:类似于 GET 请求,但只返回响应头,用于获取资源的元数据。
- OPTIONS:用于获取服务器支持的 HTTP 方法。
- TRACE:用于回显服务器收到的请求,主要用于诊断。
这些方法覆盖了基本的 HTTP 操作,允许客户端与服务器进行各种类型的交互。
下面我们看看处理响应的几种方法:
JSON 响应
router.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "hello",
"status": "success",
})
})
String 响应
router.GET("/string", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, world")
})
HTML 响应
router.LoadHTMLGlob("templates/*")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
当然还有其他格式的响应,这里就不演示了。
参数解析
路径参数
在 URL
路径当中参数是最常见的一种类型,在 gin
框架当中,针对这种情况设置了两种类型。第一种是普通的路径参数,另一种是正则匹配的 URL 地址。一开始我也有点懵,下面来看代码演示。
router.GET("/user/:id/*path", func(c *gin.Context) {
id := c.Param("id")
path := c.Param("path")
// 根据id获取用户信息或处理其他逻辑
c.String(http.StatusOK, "User ID: %s Path: %s", id, path)
})
这里 id
就是普通的路径参数, path
就是匹配参数。假如请求路径是 /user/1/age
匹配结果就是 id=1
和 path=/age
。假如路径是 /user/2/account/balance
,那么 id=2
,而 path=/account/balance
这下就清楚这两者区别,通常不咋会用到匹配的参数。
query 参数
获取 Get 请求参数的常用函数:
- func (c Context) Query*(key string) string
- func (c Context) DefaultQuery*(key, defaultValue string) string
- func (c Context) GetQuery*(key string) (string, bool)
演示代码:
func Handler(c *gin.Context) {
//获取name参数, 通过Query获取的参数值是String类型。
name := c.Query("name")
//获取name参数, 跟Query函数的区别是,可以通过第二个参数设置默认值。
name := c.DefaultQuery("name", "tizi365")
//获取id参数, 通过GetQuery获取的参数值也是String类型,
// 区别是GetQuery返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
id, ok := c.GetQuery("id")
if !ok {
// 参数不存在
}
}
POST 参数
获取 Post 请求参数的常用函数:
- func (c Context) PostForm*(key string) string
- func (c Context) DefaultPostForm*(key, defaultValue string) string
- func (c Context) GetPostForm*(key string) (string, bool)
演示代码如下:
func Handler(c *gin.Context) {
//获取name参数, 通过PostForm获取的参数值是String类型。
name := c.PostForm("name")
// 跟PostForm的区别是可以通过第二个参数设置参数默认值
name := c.DefaultPostForm("name", "tizi365")
//获取id参数, 通过GetPostForm获取的参数值也是String类型,
// 区别是GetPostForm返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
id, ok := c.GetPostForm("id")
if !ok {
// 参数不存在
}
}
对象参数
前面获取参数的方式都是一个个参数的读取,比较麻烦,Gin
框架支持将请求参数自动绑定到一个struct
对象,这种方式支持 Get/Post 请求,也支持HTTP
请求body
内容为json/xml
格式的参数。
type User struct {
Name string `json:"name" form:"name"`
Age int `json:"age" form:"age"`
}
演示代码如下:
user.GET("/login", func(c *gin.Context) {
u := &User{}
c.ShouldBind(u)
c.JSON(http.StatusOK, u)
})
分组路由
在 Gin 框架中,分组路由(Route Groups)是用于组织路由和中间件的一种有效方式。分组路由可以帮助你将相关的路由和中间件组织在一起,使代码更加清晰和易于维护。以下是如何在 Gin 框架中使用分组路由的示例:
user := r.Group("/user")
{
user.GET("/login", func(c *gin.Context) {
u := &User{}
c.ShouldBind(u)
c.JSON(http.StatusOK, u)
})
}
非常简单的语法,只需要注册一个 Group
即可,然后组内注册的 HandlerFunc
的路由前缀会加上组的 relativePath
也就是 /user
。
中间件
在 Gin 框架中,中间件(Middleware)是一个函数,它可以在处理请求之前或之后执行特定的操作。中间件通常用于执行一些通用的任务,比如日志记录、身份验证、跨域资源共享(CORS)处理等。Gin 框架支持全局中间件、路由组中间件和单个路由中间件。
基于 gin.Context
强大的功能,以及 gin
框架的优秀设计,中间件的实现方法依旧是返回 HandlerFunc
即可。下面展示一个打印日志的中间件。
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// 开始时间
t := time.Now()
// 处理请求
c.Next()
// 计算执行时间
latency := time.Since(t)
log.Printf("Latency: %v", latency)
// 获取响应状态码
status := c.Writer.Status()
log.Printf("Status: %d", status)
}
}
中间件的应用的话,有 3 种类型:全局、一组路由、单个路由。下面一次展示使用方法:
全局:
func main() {
router := gin.Default()
// 应用全局中间件
router.Use(Logger())
router.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
router.Run(":8080")
}
针对一组路由:
func main() {
router := gin.Default()
// 定义一个路由组
apiGroup := router.Group("/api")
{
// 应用中间件到路由组
apiGroup.Use(Logger())
apiGroup.GET("/users", func(c *gin.Context) {
c.String(http.StatusOK, "List of users")
})
apiGroup.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.String(http.StatusOK, "User ID: %s", id)
})
}
router.Run(":8080")
}
针对单个路由:
func main() {
router := gin.Default()
// 应用中间件到单个路由
router.GET("/admin", Logger(), func(c *gin.Context) {
c.String(http.StatusOK, "Admin page")
})
router.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
router.Run(":8080")
}
下面是我打印响应结果的中间件实践:
func Check() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("example", "12345")
//path := c.Request.URL.Path
w := &responseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = w
c.Next()
responseBody := w.body.String()
logger.Info("response", zap.String("response", responseBody))
}
}
type responseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w responseWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
其他
还有一些其他的常用 API,下面我捡着自己学到的展示一下。
获取客户端 IP:c.ClientIP()
设置模式:
// 设置 release模式
//gin.SetMode(gin.ReleaseMode)
// 或者 设置debug模式
gin.SetMode(gin.DebugMode)
Gin 框架提供了两种运行模式:Release 模式和Debug 模式。这两种模式主要区别在于日志输出和错误处理方面。通过设置不同的模式,开发者可以更好地适应开发和生产环境的需求。这个看需求选用吧。