gin框架上手实践

2024-07-30 16:27:19 浏览数 (3)

Gin框架是一个用Go语言编写的高性能Web框架,以其速度和简洁性著称。它由一个轻量级的HTTP路由器和一个中间件架构组成,能够处理大型流量并简化开发者的工作。Gin的主要特点包括内置的路由组、简洁的API设计、强大的错误处理机制、支持多种格式的请求绑定和验证,以及内置的日志记录功能。由于其性能优越和易于使用,Gin广泛应用于构建RESTful API和Web服务。其设计理念是尽可能减少繁琐的配置和代码,让开发者专注于业务逻辑,实现快速开发和部署。

PS:据部分资料显示,HTTP路由的优化,速度提升了10倍以上。但我是查到的主流资料,性能提升没这么夸张。对比我知道的一些框架,QPS提升还可以,大多数都是小于3倍的,但是内存消耗非常小,内存分配和分配时间非常小。主要原因也是因为 gin 采用了大量的 sync.Pool 优化,这一点跟 fasthttp 一样的逻辑。所以池化技术是一个性能优化的大杀器。

下面我们来开始 gin 框架的实践之旅。

依赖和安装

  1. Go 语言环境
  2. 安装 gin :go get -u github.com/gin-gonic/gin
  3. 开始撸代码

这是一个例子

首先我们先看一个最简单的例子:

代码语言:javascript复制
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.ContextGin 框架中最核心的结构之一,它提供了上下文环境,供处理HTTP请求和响应。gin.Context包含许多有用的方法和属性,使开发者能够轻松访问请求数据、设置响应数据、处理错误以及在中间件和处理器之间传递信息。

下面是 gin.Context 构造方法:

代码语言:javascript复制
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 支持的调用方法:

  1. GET:用于从服务器获取资源。
  2. POST:用于向服务器提交数据,通常用于创建资源。
  3. PUT:用于更新服务器上的资源。
  4. DELETE:用于删除服务器上的资源。
  5. PATCH:用于部分更新服务器上的资源。
  6. HEAD:类似于 GET 请求,但只返回响应头,用于获取资源的元数据。
  7. OPTIONS:用于获取服务器支持的 HTTP 方法。
  8. TRACE:用于回显服务器收到的请求,主要用于诊断。

这些方法覆盖了基本的 HTTP 操作,允许客户端与服务器进行各种类型的交互。

下面我们看看处理响应的几种方法:

JSON响应

代码语言:javascript复制
router.GET("/json", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "hello",
        "status":  "success",
    })
})

String响应

代码语言:javascript复制
router.GET("/string", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello, world")
})

HTML响应

代码语言:javascript复制
router.LoadHTMLGlob("templates/*")
router.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
        "title": "Main website",
    })
})

当然还有其他格式的响应,这里就不演示了。

参数解析

路径参数

URL 路径当中参数是最常见的一种类型,在 gin 框架当中,针对这种情况设置了两种类型。第一种是普通的路径参数,另一种是正则匹配的URL地址。一开始我也有点懵,下面来看代码演示。

代码语言:javascript复制
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=1path=/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)

演示代码:

代码语言:javascript复制
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)

演示代码如下:

代码语言:javascript复制
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格式的参数。

代码语言:javascript复制
type User struct {  
    Name string `json:"name" form:"name"`  
    Age  int    `json:"age"  form:"age"`  
}

演示代码如下:

代码语言:javascript复制
user.GET("/login", func(c *gin.Context) {  
    u := &User{}  
    c.ShouldBind(u)  
    c.JSON(http.StatusOK, u)  
})

分组路由

在Gin框架中,分组路由(Route Groups)是用于组织路由和中间件的一种有效方式。分组路由可以帮助你将相关的路由和中间件组织在一起,使代码更加清晰和易于维护。以下是如何在Gin框架中使用分组路由的示例:

代码语言:javascript复制
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 即可。下面展示一个打印日志的中间件。

代码语言:javascript复制
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种类型:全局、一组路由、单个路由。下面一次展示使用方法:

全局:

代码语言:javascript复制
func main() {
    router := gin.Default()
    // 应用全局中间件
    router.Use(Logger())
    router.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })
    router.Run(":8080")
}

针对一组路由:

代码语言:javascript复制
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")
}

针对单个路由:

代码语言:javascript复制
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")
}

下面是我打印响应结果的中间件实践:

代码语言:javascript复制
  
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()

设置模式:

代码语言:javascript复制
// 设置 release模式  
//gin.SetMode(gin.ReleaseMode)  
// 或者 设置debug模式  
gin.SetMode(gin.DebugMode)

Gin框架提供了两种运行模式:Release 模式Debug 模式。这两种模式主要区别在于日志输出和错误处理方面。通过设置不同的模式,开发者可以更好地适应开发和生产环境的需求。这个看需求选用吧。

0 人点赞