Gin框架学习笔记 | 青训营

259 阅读18分钟

1. Gin框架简介

1.1 Gin是什么?

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,但是性能要好得多,由于采用的路由库是基于 httprouter做的,速度提高了 40 倍。

1.2 为什么要使用Gin?

直接使用标准库开发Web服务可以实现简单的功能,但在实际开发中会遇到一些限制和不便之处。例如:

  1. 路由注册能力有限:标准库提供的路由功能只支持精确匹配,无法处理通配符、路径参数等场景。而Gin框架提供了更强大的路由功能,可以通过通配符和路径参数等方式灵活定义路由规则,满足更多实际开发需求。
  2. 请求和响应处理繁琐:标准库暴露给开发者的函数参数需要直接从请求中读取数据、反序列化,响应时手动序列化、设置Content-Type、写响应内容,这些操作相对繁琐。而Gin框架封装了这些操作,提供了简洁的API,使请求和响应处理更加方便和高效。
  3. 业务和非业务代码耦合:直接基于标准库开发时,业务和非业务代码不可避免地会耦合在一起,导致代码可读性和可维护性下降。而Gin框架提供了中间件机制,可以在不侵入业务代码的情况下,对请求和响应进行前置或后置处理,使业务代码与非业务代码分离,提高代码的可读性和可维护性。

虽然标准库可以实现简单的Web服务,但在实际开发中,为了满足更多需求并提高开发效率和代码质量,使用Gin框架是一个更好的选择。

1.3 Gin 优点

  • 较轻量级,事实上是加了路由和模板功能的net/http库
  • 第三方组件兼容性好,可以自由选择第三方组件

1.4 Gin 缺点

  • 过于轻量级,初学者初期需要花时间去寻找学习第三方组件的使用方式,并将其融合到项目中
  • 官方文档较简单
  • 没有工程化的组件,需要自行研究引入。

1.5 下载安装 gin 包

 go get -u github.com/gin-gonic/gin

1.6 一个简单的例子

通过上面的了解,相信你已经对Gin框架产生了浓厚的兴趣,那么接下来让我们看看一个简单的例子:

  • 首先新建一个项目(gintest),然后在项目的根目录下新建一个go文件(hello_world.go)代码如下:
 package main
 ​
 import (
     "github.com/gin-gonic/gin"
     "net/http"
 )
 ​
 func main() {
     router := gin.Default()
     router.GET("/", func(c *gin.Context) {
         c.String(http.StatusOK, "Hello World")
     })
     router.Run(":8000") 
 }
  • 代码部分完成了,然后在控制台安装包go get -u github.com/gin-gonic/gin,运行程序go run hello_world.go

  • 打开浏览器并访问指定的端口:http://127.0.0.1:8000/

    图片

通过以上代码,我们使用Gin框架创建一个HTTP服务器,并定义一个路由规则。当用户访问根路径"/"时,服务器会返回一个"Hello World"的字符串作为响应。让我们分析一下上面的代码:

  1. 导入所需的包:

    • github.com/gin-gonic/gin:Gin框架的主要包,用于创建路由引擎和处理HTTP请求。
    • net/http:Go语言的标准库,用于处理HTTP请求和响应。
  2. main函数:

    • 程序的入口函数。
    • 创建一个Gin路由引擎实例,使用gin.Default()方法创建一个默认配置的引擎。
  3. 路由定义:

    • 使用router.GET()方法定义一个GET请求的路由规则。
    • 路由规则中的第一个参数是要匹配的路径,这里是根路径"/"。
    • 第二个参数是一个处理函数,当请求匹配到该路由规则时,会执行这个函数。
  4. 处理函数:

    • 匿名函数func(c *gin.Context) { ... }作为处理函数。
    • 处理函数的参数是一个gin.Context类型的指针,用于处理请求和生成响应。
    • 在处理函数中,使用c.String()方法将字符串"Hello World"作为响应内容发送给客户端。
    • http.StatusOK是一个HTTP状态码,表示请求成功。
  5. 启动服务器:

    • 使用router.Run(":8000")方法启动HTTP服务器,并监听在8000端口上。
    • 这样,当有请求到达服务器的根路径时,会执行定义的处理函数并返回响应。

2. Gin框架的路由

2.1 启动服务器

2.1.1 Run()方法启动

 router.Run()

这种方式是使用Gin框架提供的Run()方法来启动服务器。它默认监听在localhost:8080地址上,是一种简便的启动方式。

2.1.2 http自定义启动

除了使用router.Run()的方式默认启动外,还可以用http.ListenAndServe()方法启动,比如

 func main() {
     router := gin.Default()
     http.ListenAndServe(":8080", router)
 }

或者自定义HTTP服务器的配置:

 func main() {
     router := gin.Default()
 ​
     s := &http.Server{
         Addr:           ":8080",
         Handler:        router,
         ReadTimeout:    10 * time.Second,
         WriteTimeout:   10 * time.Second,
         MaxHeaderBytes: 1 << 20,
     }
     s.ListenAndServe()
 }

这种方式是使用Go语言的标准库中的http.ListenAndServe()方法来启动服务器,同时将路由引擎实例作为参数传递给该方法。通过这种方式,你可以更加灵活地配置服务器的地址和其他参数。

2.1.3 区别与联系

  • 区别:

    • router.Run() 是 Gin 框架提供的方法,它使用了默认的 http.Server 配置,监听在 localhost:8080 上。
    • http.ListenAndServe() 是 Go 语言标准库中的方法,它接受一个地址和一个处理器作为参数,可以更加灵活地配置服务器的地址和其他参数。
  • 联系:

    • 通过查看Run函数的源代码,我们可以发现router.Run() 实际上内部调用了 http.ListenAndServe(),并传递了 Gin 框架的路由引擎实例作为处理器。这两种方法本质是一样的。
    •  // Run方法源代码
       func (engine *Engine) Run(addr ...string) (err error) {
           defer func() { debugPrintError(err) }()
       ​
           if engine.isUnsafeTrustedProxies() {
               debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
                   "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
           }
       ​
           address := resolveAddress(addr)
           debugPrint("Listening and serving HTTP on %s\n", address)
           err = http.ListenAndServe(address, engine.Handler())
           return
       }
      

总而言之,第一种代码是使用Gin框架提供的简便方式启动服务器,而第二种代码是使用Go语言的标准库来启动服务器,并具有更多的配置选项。你可以根据需要选择适合的方式来启动你的Gin应用程序。

2.2 路由

2.2.1 web路由的基本概念

路由是Web应用中的一个重要概念,它决定了如何将客户端的请求映射到相应的处理程序上。简单来说,路由是将URL与处理逻辑进行关联的过程

在Web应用中,客户端通过发送HTTP请求来与服务器进行交互。每个HTTP请求都包含一个URL,用于标识客户端要访问的资源。而路由则决定了当收到这个URL请求时,应该调用哪个处理程序来处理该请求。

为什么需要路由呢?主要有以下几个原因:

  1. 动态资源映射: Web应用中的资源通常是动态生成的,而不是静态的文件。通过路由,可以将请求的URL与相应的处理程序绑定,从而动态地生成所需的资源。
  2. 组织和管理请求: Web应用可能包含多个页面和功能,路由可以帮助我们将不同的URL请求按照业务逻辑进行组织和管理。通过定义不同的路由规则,可以将请求分发到不同的处理程序中,实现代码的模块化和可维护性。
  3. URL参数解析: 在Web应用中,URL通常携带一些参数,如查询参数、路径参数等。通过路由,可以方便地从URL中提取这些参数,并将其传递给相应的处理程序进行处理。
  4. 中间件支持: 路由也提供了中间件的支持,中间件可以在请求到达处理程序之前或之后执行一些公共的逻辑。通过路由,我们可以注册和使用中间件,实现日志记录、身份验证、错误处理等功能。

总而言之,路由在Web应用中发挥着重要的作用,它决定了客户端请求的流向,将请求与相应的处理程序进行关联。通过合理使用路由,可以使Web应用更加灵活、模块化和易于管理。

2.2.2 gin框架中的路由引擎

Gin路由引擎的主要作用是处理HTTP请求和路由分发。它负责将接收到的HTTP请求与相应的处理器函数进行匹配,并将请求交给匹配到的处理器函数进行处理。在gin框架中有两种方法创建。

  1. 创建默认配置的路由引擎实例

     router := gin.Default()
    

    通过查看gin.Default()方法的源代码,我们可以知道gin.Defaut()方法是对gin.New()方法的封装,但是其中加入了Logger, Recovery这两个中间件,这些中间件可以记录请求日志和处理panic,提供了一些基本的功能和保护措施。

  2. 创建空白路由引擎实例

     router := New() 
    

    使用gin.New()方法可以创建一个空白的路由引擎实例。空白实例没有默认配置中间件,需要根据需要手动添加。

2.2.3 路由注册

当我们在Gin框架中注册路由时,可以使用路由引擎实例的各种HTTP方法来定义路由规则。这些HTTP方法对应了不同的请求类型,如GET、POST、PUT、DELETE等。通过注册路由规则,我们可以将不同的URL路径和HTTP方法映射到相应的处理器函数上。

下面我们来详细解读一下路由注册的方式:

  1. 使用GET方法注册GET请求的路由:

     router.GET("/path", handlerFunc)
    

    上述代码中,使用GET方法定义了一个GET请求的路由规则。当收到匹配的GET请求时,Gin路由引擎会调用handlerFunc作为处理器函数来处理请求。

  2. 使用POST方法注册POST请求的路由:

     router.POST("/path", handlerFunc)
    

    类似地,使用POST方法定义了一个POST请求的路由规则。当收到匹配的POST请求时,Gin路由引擎会调用handlerFunc来处理请求。

  3. 使用PUT方法注册PUT请求的路由:

     router.PUT("/path", handlerFunc)
    

    使用PUT方法定义了一个PUT请求的路由规则。当收到匹配的PUT请求时,Gin路由引擎会调用handlerFunc来处理请求。

  4. 使用DELETE方法注册DELETE请求的路由:

     router.DELETE("/path", handlerFunc)
    

    使用DELETE方法定义了一个DELETE请求的路由规则。当收到匹配的DELETE请求时,Gin路由引擎会调用handlerFunc来处理请求。

除了上述常用的HTTP方法,Gin还提供了其他HTTP方法的路由注册方式。比如,使用PATCH方法注册PATCH请求的路由,使用HEAD方法注册HEAD请求的路由,使用OPTIONS方法注册OPTIONS请求的 路由等。这些方法的使用方式类似,只需要替换对应的HTTP方法即可。

另外,Gin还提供了Any方法来注册可以匹配任意HTTP方法的路由。使用Any方法时,需要传入一个路由路径和处理器函数。Gin会将该路由路径与所有HTTP方法进行匹配,从而可以对任意HTTP方法的请求进行处理。

 router.Any("/path", handlerFunc)

通过以上方式,我们可以在Gin框架中使用不同的HTTP方法来注册路由规则,根据不同的请求类型来调用相应的处理器函数。这样可以实现灵活的路由处理和请求分发。由此可见通过Gin框架,我们可以轻松地采用RESTful风格的软件架构。

2.2.4 路由匹配

路由匹配是指在Gin框架中,将收到的HTTP请求与路由规则进行匹配的过程。当有请求到达时,Gin路由引擎会根据请求的URL路径和HTTP方法,找到与之匹配的路由规则,并将请求分发给对应的处理器函数进行处理。值得注意的是Gin不支持正则匹配,需要自实现。

Gin框架提供了多种路由匹配的方式,常用的方式包括:

  • 精确匹配:URL路径必须与路由规则完全匹配。
  • 参数匹配:URL路径中的某些部分可以作为参数进行匹配。
  • 通配符匹配:URL路径可以匹配任意的字符或者一定范围的字符。

以下是一些示例:

  1. 精确匹配:

     router.GET("/users", func(c *gin.Context) {
         c.JSON(http.StatusOK, "/users")
     })
    

    上述代码中,路由规则/users会匹配URL路径为/users的GET请求,/users/的GET请求默认会被重定向到/users

  2. 参数匹配:

     router.GET("/users/:id", func(c *gin.Context) {
         id := c.Param("id")
         c.JSON(http.StatusOK, "%s", id)
     })
    

    在上述代码中,路由规则/users/:id可以匹配URL路径为/users/123的GET请求(匹配不到/user/123/12),其中:id是一个参数,表示用户的ID。通过c.Param方法可以获取到该参数的值,获取的参数为字符串类型。

  3. 通配符匹配:

     router.GET("/users/1/*path", func(c *gin.Context) {
         path := c.Param("path")
         c.JSON(http.StatusOK, "%s", path)
     })
    

    在上述代码中,路由规则/users/*path可以匹配URL路径为/users/abc/def的GET请求,返回的字符串为/abc/def,其中*path表示路径的一部分,可以匹配任意字符。

2.2.4 处理器函数

处理器函数是用于处理请求的函数,它接收一个gin.Context指针类型的参数,gin.Context包含了请求的上下文信息,如请求参数、请求头、响应等。

在Gin框架中,处理器函数负责处理路由匹配到的请求,并生成相应的响应。处理器函数通常包含以下几个步骤:

  1. 解析请求参数:可以通过gin.Context的方法来获取请求参数,如Query方法获取URL查询参数,PostForm方法获取表单参数,BindJSON方法解析JSON请求体等。
  2. 处理业务逻辑:根据请求的目的和操作,编写相应的业务逻辑代码来处理请求。这可能包括查询数据库、调用其他服务、处理数据等。
  3. 构建响应:根据业务逻辑的结果,构建相应的响应数据。可以使用gin.Context的方法来设置响应状态码、响应头和响应体。
  4. 返回响应:通过gin.Context的方法将响应发送给客户端。

以下是一个简单的处理器函数示例:

 func getUser(c *gin.Context) {
     userID := c.Param("id") // 获取URL路径参数
     // 根据userID查询用户信息的业务逻辑
     user := getUserByID(userID)
     if user == nil {
         c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
         return
     }
     c.JSON(http.StatusOK, user)
 }

在上面的示例中,getUser函数是用于处理获取用户信息的请求。它首先通过c.Param方法获取URL路径中的参数,然后根据该参数查询用户信息。如果找不到用户,返回404状态码和相应的错误消息;如果找到用户,返回200状态码和用户信息。

处理器函数是路由匹配后实际处理请求的地方,通过编写合适的处理器函数,我们可以实现对资源的增删改查等操作,并提供合适的响应给客户端。

2.2.5 参数解析与绑定

当处理 HTTP 请求时,我们通常需要从请求中获取参数,并且可能还需要对参数进行验证。Gin 框架提供了一些方便的方法来解析和绑定参数,并使用 binding 标签进行验证。

以下是一些常用的参数获取方式:

  1. 获取查询参数:

    使用 c.Query() 方法可以获取查询参数,例如 URL 中的查询字符串参数。以下是一个示例:

     func getUser(c *gin.Context) {
         name := c.Query("name")
         age := c.Query("age")
     ​
         // 处理逻辑...
     }
    

    在上述示例中,c.Query() 方法用于获取查询字符串参数,如 /user?name=John&age=20 中的 nameage 参数。

  2. 获取路径参数:

    使用 c.Param() 方法可以获取路径参数,例如 URL 中的占位符参数。以下是一个示例:

     func getUser(c *gin.Context) {
         id := c.Param("id")
     ​
         // 处理逻辑...
     }
    

    在上述示例中,c.Param() 方法用于获取 URL 中的路径参数,如 /user/:id 中的 id 参数。

  3. 获取表单参数:

    使用 c.PostForm() 方法可以获取表单参数,例如 POST 请求中的表单数据。以下是一个示例:

     func createUser(c *gin.Context) {
         name := c.PostForm("name")
         email := c.PostForm("email")
     ​
         // 处理逻辑...
     }
    

    在上述示例中,c.PostForm() 方法用于获取 POST 请求的表单参数。

  4. 获取请求体参数:

    使用 c.ShouldBind() 方法可以将请求体中的参数绑定到结构体中。以下是一个示例:

     type User struct {
         Name  string `form:"name" json:"name" binding:"required"`
         Email string `form:"email" json:"email" binding:"required,email"`
     }
     ​
     func createUser(c *gin.Context) {
         var user User
     ​
         if err := c.ShouldBind(&user); err != nil {
             c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
             return
         }
     ​
         // 处理逻辑...
     }
    

    在上述示例中,我们定义了一个 User 结构体,并使用 formjson 标签指定了参数的解析方式。通过 c.ShouldBind() 方法将请求体中的参数绑定到结构体中,并进行参数验证。

通过以上方式,我们可以方便地在 Gin 框架中获取和解析参数,并进行相应的处理和验证。

2.2.6 中间件

中间件是在请求到达处理器函数之前或之后执行的一系列函数。在Gin框架中,中间件可以用于执行一些公共的逻辑,如日志记录、身份验证、错误处理等。

在Gin框架中,可以通过Use方法注册中间件。中间件函数接收一个gin.Context参数,可以在函数中执行一些逻辑,并决定是否继续执行后续的中间件或处理器函数。

以下是一个简单的中间件示例:

 func loggerMiddleware(c *gin.Context) {
     log.Println("Request received") // 记录日志
     c.Next() // 继续执行后续的中间件或处理器函数
     log.Println("Request handled") // 记录日志
 }
 ​
 func main() {
     router := gin.Default() // 创建一个默认的路由引擎实例
 ​
     router.Use(loggerMiddleware) // 注册中间件
 ​
     router.GET("/users", func(c *gin.Context) {
         c.JSON(http.StatusOK, gin.H{"message": "Hello, users"})
     })
 ​
     router.Run(":8080") // 启动HTTP服务器
 }

在上述示例中,loggerMiddleware函数是一个简单的中间件函数,用于记录请求的日志。在main函数中,通过router.Use方法注册了该中间件。

当有请求到达时,Gin框架会先执行中间件函数,然后再执行后续的处理器函数。在中间件函数中,可以执行一些公共的逻辑,如记录请求日志、验证身份等。

通过使用中间件,我们可以在请求到达处理器函数之前或之后执行一些公共的逻辑,提供更加灵活和可复用的代码。

2.2.7 静态文件服务

静态文件指的是Web应用中的一些固定文件,如HTML、CSS、JavaScript、图片、视频等。这些文件不需要经过处理,直接返回给浏览器即可。在Gin框架中,静态文件通常存储在服务器的某个目录下,例如/public或/static目录。

当使用 Gin 框架处理静态文件时,可以使用以下三种方法中的一种:

  1. 使用 Static() 方法:

     func main() {
         router := gin.Default()
     ​
         // 配置静态文件服务
         router.Static("/static", "./static")
     ​
         router.Run(":8080")
     }
    

    在上述示例中,我们将 /static 路径映射到 ./static 文件夹。这意味着当有请求发送到 /static 路径时,Gin 框架会自动在 ./static 文件夹下查找并提供对应的静态文件。

    假设在 ./static 文件夹下有一个名为 1.mp4 的视频文件。在浏览器中访问 http://localhost:8080/static/1.mp4,它将加载并播放 1.mp4 视频文件。请确保 ./static 文件夹下存在名为 1.mp4 的视频文件,并确保你的服务器已经在监听 8080 端口。这样,当你访问 http://localhost:8080/static/1.mp4 时,浏览器将加载并播放该视频文件。

    通过配置静态文件服务,Gin 框架可以帮助你提供静态文件,并自动处理文件的路由。这样,你可以通过浏览器轻松访问和播放静态视频文件。

  2. 使用 StaticFS() 方法:

     func main() {
         router := gin.Default()
     ​
         // 将已存在的文件系统映射到 /static 路径上
         router.StaticFS("/static", http.Dir("./static"))
     ​
         router.Run(":8080")
     }
    

    在上述示例中,我们使用 StaticFS() 方法将一个已存在的文件系统映射到 /static 路径上。使用 http.Dir() 函数指定静态文件所在的文件夹路径。

    image-20230814003750104

  3. 使用 StaticFile() 方法:

     func main() {
         router := gin.Default()
     ​
         // 处理单个静态文件
         router.StaticFile("/favicon.ico", "./static/favicon.ico")
     ​
         router.Run(":8080")
     }
    

    在上述示例中,我们使用 StaticFile() 方法来处理单个静态文件。我们将 /favicon.ico 路径映射到 ./static/favicon.ico 文件。

通过以上三种方法之一,Gin 框架可以自动处理静态文件的路由和提供相应的文件内容。你可以根据自己的需求选择适合的方法来处理静态文件。

参考文献

一文摸清gin框架原理 - 知乎 (zhihu.com)

gin框架教程