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 模式。这两种模式主要区别在于日志输出和错误处理方面。通过设置不同的模式,开发者可以更好地适应开发和生产环境的需求。这个看需求选用吧。
评论