go-web框架Gin特性介绍及实践(一)

2,401 阅读5分钟

1、gin简介

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

Gin 是一款由 golang 开发的HTTP Web 框架,它具有类似Martini 的 API 同时拥有更高的性能 ———— 速度提升40倍。如果您需要出色的性能,请使用Gin。

以上是Gin官方对该web框架的定位。截止目前为止,gin项目已经拥有 66.5k star 和 113.4k user,作为时下最流行的 高性能 go web 框架,gin 具有以下关键特征:

  • 高性能(得益于httprouter,基于Radix树的路由、内存占用小、无反射)
  • 中间件(灵活、可扩展)
  • 无崩溃(内置异常抓取和恢复机制,服务始终可用)
  • JSON 校验(方便解析并校验请求中的Json)
  • 路由分组 (支持分组配置,支持嵌套而不降低性能)
  • 错误管理
  • 内置渲染(为JSON、XML 和 HTML 渲染提供了API)
  • 可扩展 (自定义中间件)

2、快速开始

(1)、安装和更新

安装和更新

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

代码中引用

import "github.com/gin-gonic/gin"

(2)、启动一个gin服务

func RunGin() {
	r := gin.Default()

	r.GET("/health", handler.HealthHandler)

	err := r.Run(configs.GetConfig().Gin.Listen)
	if err != nil {
		log.Fatalf("run gin server error: %s", err.Error())
	}

	log.Println("gin server success, http listening", addr)
}

//主函数
func Main(t *testing.T) {
	RunGin()
}

3、主要特性介绍

(1)、高性能

Gin的高性能得益于其路由的实现基于httprouter这个高性能HTTP请求路由框架开发。

httprouter的快速匹配通过基数树这种数据结构实现,该数据结构在处理HTTP请求路由这种存在较多公共前缀、路由数量较少但每条记录较长的匹配场景下具有先天优势。

(2)、中间件

gin 官方推荐了很多第三方贡献的优秀中间件,同时也支持用户自定义中间件的开发,创建一个中间件非常简单,只需要实现一个 gin.HandlerFunc 方法即可。

自定义中间件示例

例如我们开发三个简单中间件分别用于统计全部接口、健康检测接口调用以及业务接口的鉴权。

// 自定义统计全部接口调用中间件
func CallCount() gin.HandlerFunc {
	return func(c *gin.Context) {
		log.Println("call gin server")
	}
}

// 自定义统计group 分组 post 方法接口调用中间件
func CallPost() gin.HandlerFunc {
	return func(c *gin.Context) {
		log.Println("call post server")
	}
}


// 自定义鉴权中间件
func Authorization(auth string) gin.HandlerFunc {
	return func(c *gin.Context) {
		var p common.AuthParam
		var err error
		if err = c.ShouldBindBodyWith(&p, binding.JSON); err != nil {
			errMsg := fmt.Sprintf("bind auth param error: %s", err.Error())
			c.JSON(http.StatusOK, common.Response{
				Code:    common.BadCode,
				Message: errMsg,
				Date:    nil,
			})
			return
		}

		if p.Auth != auth {
			errMsg := fmt.Sprintf("auth [%s] error", p.Auth)
			c.JSON(http.StatusOK, common.Response{
				Code:    common.BadCode,
				Message: errMsg,
				Date:    nil,
			})
			return
		}

	}
}

上述中间件的实现逻辑都非常简单,只是为了后续展示不同类型中间件的使用。

需要注意的一点是,当中间件和业务Handler中都需要读取gin.context中的c.Request.Body数据时,必须全部使用ShouldBindBodyWith方法来实现,否则将发生 EOF 异常,具体原因参见上一篇文章 Gin多次读取HTTP请求体中body数据(ShouldBindBodyWith)

中间件的分类与调用

中间件按照使用位置及方法的不同可以分为三类:

  • 全局中间件
  • 局部中间件
  • 单路由中间件
func RunGin() {
	r := gin.Default()
	r.Use(CallCount())
	pprof.Register(r)

	r.GET("/health", handler.HealthHandler)
	group := r.Group("/api/v1", CallPost())
	group.POST("/json/echo", Authorization("123456"),handler.JsonEchoHandler)
	group.POST("/urlencoded/echo", handler.UrlencodedEchoHandler)
	group.POST("/formdata/echo", handler.FormDataEchoHandler)er)

	err := r.Run(configs.GetConfig().Gin.Listen)
	if err != nil {
		log.Fatalf("run gin server error: %s", err.Error())
	}

	log.Println("gin server success, http listening", addr)
}

三种不同中间件的具体区分方法如下:

  • 使用 r.Use(CallCount()) 注册的中间件就是全局中间件,它将在所有的路由中生效。
  • 使用 group := r.Group("/api/v1", CallPost()) 注册的就是局部中间件,它只在 /api/v1 这一分组的路由中生效。
  • 使用 group.POST("/json/echo", Authorization("123456"),handler.JsonEchoHandler) 注册的是单路由中间件,他只在 ///api/v1/json/echo 这一指定路由中生效。

(3)、无崩溃

Gin 内置了 panic recover 机制,它可以恢复gin服务执行过程中的任务 panic ,同时在返回结果中写入 HTTP状态码 500。因此当请求内部发生异常时,并不会影响整个系统的正常运行。

err = sendResuest error:get bad htp status code 500, resp = {{Status:500 Internal Server Error StatusCode:500 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Content-Length:[0] Date:[Thu, 23 Feb 2023 10:04:05 GMT]] Body:0xc000286020 ContentLength:0 TransferEncoding:[] Close:false Uncompressed:false Trailer:map[] Request:0xc00018c100 TLS:}

Gin 内置 panic recover 机制是通过 中间件实现的。如果使用gin.Default() 创建一个 gin 服务,会自动添加 Recover中间件。同时也可以通过 r.Use(gin.Recovery())手动添加。

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

(4)、路由分组

为了对HTTP路由进行管理,Gin支持路由的分组,我们可以基于接口的版本、方法或者业务逻辑对多个具有进行分组,从而实现灵活的管理。例如上文中,我们结合中间件,可以对不同的路由分组,实现不同的鉴权、统计或者其他复杂逻辑的管理。


func RunGin() {
	r := gin.Default()
	r.Use(CallCount())

	r.GET("/health", handler.HealthHandler)
	
	group := r.Group("/api/v1", CallPost())
	group.POST("/json/echo", Authorization("123456"),handler.JsonEchoHandler)
	group.POST("/urlencoded/echo", handler.UrlencodedEchoHandler)
	group.POST("/formdata/echo", handler.FormDataEchoHandler)

	groupV2 := r.Group("/api/v2")
	groupV2.POST("/json/echo",handler.JsonEchoHandler)
	groupV2.POST("/urlencoded/echo", handler.UrlencodedEchoHandler)
	groupV2.POST("/formdata/echo", handler.FormDataEchoHandler)
	

	err := r.Run(configs.GetConfig().Gin.Listen)
	if err != nil {
		log.Fatalf("run gin server error: %s", err.Error())
	}

	log.Println("gin server success, http listening", addr)
}

(5)、内置渲染

Gin 内置了 String、Json、Yaml、Xml和Proto等渲染方式。如果对同一个接口提供上述五种渲染方式进行验证,具体效果如下:

func RunGin() {
	r := gin.Default()

	health := r.Group("/health")
    health.GET("/string", handler.HealthHandler)
	health.GET("/json", handler.HealthJsonHandler)
	health.GET("/yaml", handler.HealthYamlHandler)
	health.GET("/xml", handler.HealthXmlHandler)

	err := r.Run(configs.GetConfig().Gin.Listen)
	if err != nil {
		log.Fatalf("run gin server error: %s", err.Error())
	}

	log.Println("gin server success, http listening", addr)
}
  • string渲染
func HealthHandler(c *gin.Context) {
	c.String(http.StatusOK, "ok")
}

结果样式:

map[message:success state:0]
  • Json渲染
func HealthJsonHandler(c *gin.Context) {
	c.JSON(http.StatusOK,gin.H{"state":0,"message":"success"})
}

结果样式:

{
    "message": "success",
    "state": 0
}
  • Yaml渲染
func HealthYamlHandler(c *gin.Context) {
	c.YAML(http.StatusOK,gin.H{"state":0,"message":"success"})
}

结果样式:

message: success
state: 0
  • Xml渲染
func HealthXmlHandler(c *gin.Context) {
	c.XML(http.StatusOK,gin.H{"state":0,"message":"success"})
}

结果样式:

<map>
    <state>0</state>
    <message>success</message>
</map>