云原生时代Go最受欢迎Web开源框架Gin原理与实战
@
概述
定义
Gin 官网地址 https://gin-gonic.com/ 源码release最新版本v1.9.1
Gin 官网文档地址 https://gin-gonic.com/docs/
Gin 源码地址 https://github.com/gin-gonic/gin
Gin是目前使用最广泛、最快的全功能web框架之一,采用Go语言(Golang)编写HTTP 服务,与它类似如martini-like API ,但Gin性能更好,基于httprouter其速度快了40倍。
Gin是一种用于构建Web应用程序的Go语言框架,具有高性能、易于使用、轻量级和灵活的特点。 Gin提供了许多功能,例如路由、中间件、错误处理等。同时,Gin还可以与许多其他Go语言库和框架无缝集成。 Gin的设计目的是提供一种快速、可靠和高效的方式来构建Web应用程序,以满足现代Web应用程序的需求;它的文档和社区支持也非常好,因此它成为了Go语言中最受欢迎的Web框架之一。
特点
- 快:基于Radix tree的路由,内存占用小;没有反射,可预测的API性能。
- 中间件支持:传入的HTTP请求可以由一系列中间件和最终操作来处理。例如:Logger, Authorization, GZIP,最后在DB中发布消息。
- 无故障:Gin可以捕获HTTP请求期间发生的panic并恢复它保证服务器的可用性;例如可以向哨兵报告这种panic。
- JSON验证:Gin可以解析和验证请求的JSON,例如检查是否存在所需值。
- 路由分组:更好地组织路由,如需要授权与不需要授权、不同的API版本;此外路由组可以无限嵌套且不会降低性能。
- 错误管理:Gin提供了一种方便的方法来收集HTTP请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送。
- 呈现内置:Gin为JSON、XML和HTML渲染提供了一个易于使用的API。
- 可扩展的:非常简单通过自定义创建新的中间件实现扩展功能。
概览导图

使用
快速入门
前置环境:需要go 1.13及以上版本
# 下载并安装
go get -u github.com/gin-gonic/gin
创建quick_start.go文件,导入gin库
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	// 配置路由映射
	r.GET("/hello", func(c *gin.Context) {
		c.String(http.StatusOK, "hello,welcome to go world!")
	})
    // 监听端口,默认为0.0.0.0:8080
	r.Run()
}
启动运行,访问http://localhost:8080/hello可以看到成功返回信息
go run quick_start.go

HTTP 方法使用
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func demoGet(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo get",
	})
}
func demoPost(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo post",
	})
}
func demoPut(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo put",
	})
}
func demoDelete(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo delete",
	})
}
func demoPatch(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo patch",
	})
}
func demoHead(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo head",
	})
}
func demoOptions(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo options",
	})
}
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	// 配置路由映射
	r.GET("/DemoGet", demoGet)
	r.POST("/DemoPost", demoPost)
	r.PUT("/DemoPut", demoPut)
	r.DELETE("/DemoDelete", demoDelete)
	r.PATCH("/DemoPatch", demoPatch)
	r.HEAD("/DemoHead", demoHead)
	r.OPTIONS("/DemoOptions", demoOptions)
	r.Run() // 监听端口,默认为0.0.0.0:8080
}
运行程序,通过HTTP请求工具如PostMan输入请求地址http://localhost:8080/DemoPost,返回预期结果

参数获取
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func getVal(c *gin.Context) {
	name := c.Query("name")
	addr := c.DefaultQuery("addr", "home")
	c.JSON(http.StatusOK, gin.H{
		"name": name,
		"addr": addr,
	})
}
func postVal(c *gin.Context) {
	name := c.PostForm("name")
	addr := c.DefaultPostForm("addr", "home")
	c.JSON(http.StatusOK, gin.H{
		"name": name,
		"addr": addr,
	})
}
func restVal(c *gin.Context) {
	name := c.Param("name")
	addr := c.Param("addr")
	c.JSON(http.StatusOK, gin.H{
		"name": name,
		"addr": addr,
	})
}
func postMapVal(c *gin.Context) {
	ids := c.QueryMap("ids")
	names := c.PostFormMap("names")
	c.JSON(http.StatusOK, gin.H{
		"ids":   ids,
		"names": names,
	})
}
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	// 配置路由映射
	r.GET("/GetVal", getVal)
	r.POST("/PostVal", postVal)
	r.POST("/PostMapVal", postMapVal)
	r.POST("/RestVal/:name/:addr", restVal)
	r.Run() // 监听端口,默认为0.0.0.0:8080
}
运行程序,通过HTTP请求工具如PostMan输入请求地址http://localhost:8080/GetVal?name=itxiaoshen,返回预期结果

用Post提交方式,输入请求地址http://localhost:8080/PostVal并选择Body的中form-data或者x-www-form-urlencoded填写参数,返回预期结果

输入请求地址http://localhost:8080/PostMapVal?ids[a]=hello&ids[b]=world,在Body体填写相应的数组参数值,通过数组返回并输出预期结果

输入请求地址http://localhost:8080/RestVal/lixuefeng/qinghua,在url路径参数中输入相应的参数值,返回预期结果

参数绑定
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
type Person struct {
	Name    string `form:"name"`
	Address string `form:"addr"`
	Age     int
}
type PersonUri struct {
	Name    string `uri:"name" binding:"required"`
	Address string `uri:"addr" binding:"required"`
	Age     int    `uri:"age" binding:"required"`
}
func bindVal(c *gin.Context) {
	var person Person
	if c.ShouldBind(&person) == nil {
		c.JSON(http.StatusOK, gin.H{
			"name": person.Name,
			"addr": person.Address,
			"age":  person.Age,
		})
	}
}
func personMethod(c *gin.Context) {
	var person PersonUri
	if err := c.ShouldBindUri(&person); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": err})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"name": person.Name,
		"addr": person.Address,
		"age":  person.Age,
	})
}
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	// 配置路由映射
	r.GET("/BindVal", bindVal)
	r.GET("/person/:name/:addr/:age", personMethod)
	r.Run() // 监听端口,默认为0.0.0.0:8080
}
运行程序,输入请求地址http://localhost:8080/BindVal?name=wangchuanjun&addr=zhengjiang&age=20,由于传入age字段与Person的Age字段名没匹配上,因此Age字段获取不到值采用的默认值0输出

通过uri路径参数输入请求地址http://localhost:8080/person/yangju/chengsan/26,返回预期结果

自定义日志输出
package main
import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)
func main() {
	r := gin.New()
	// LoggerWithFormatter中间件将把日志写入gin.DefaultWriter,默认为gin.DefaultWriter = os.Stdout
	r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
		// 自定义输出日志
		return fmt.Sprintf("custom log format:%s -------- [%s] \"%s %s %d %s \"%s\" %s\"%s\n",
			param.ClientIP,
			param.Method,
			param.Path,
			param.Request.Proto,
			param.StatusCode,
			param.Latency,
			param.Request.UserAgent(),
			param.ErrorMessage,
			param.TimeStamp.Format(time.RFC1123),
		)
	}))
	r.Use(gin.Recovery())
	r.GET("/hello", func(c *gin.Context) {
		c.String(http.StatusOK, "hello,welcome to go world!")
	})
	r.Run(":8080")
}
启动运行,访问http://localhost:8080/hello,查看运行的控制台日志可以看到自定义日志输出

自定义中间件
package main
import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"time"
)
// 返回gin.HandlerFunc函数
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		// 设置 userId 变量
		c.Set("userId", "10001")
		// 请求前
		c.Next()
		// 请求后
		latency := time.Since(t)
		log.Print(latency)
		// access the status we are sending
		status := c.Writer.Status()
		log.Println(status)
	}
}
func main() {
	r := gin.New()
    // 使用自定义中间件
	r.Use(Logger())
	r.GET("/hello", func(c *gin.Context) {
		userId := c.MustGet("userId").(string)
		c.String(http.StatusOK, "userId="+userId)
	})
	// 监听 0.0.0.0:8080
	r.Run(":8080")
}
启动运行,访问http://localhost:8080/hello,查看运行的控制台日志可以看到输出响应的日志,也响应userId结果

路由组
路由组可以多级嵌套,实现细粒度控制
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func demoGet(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo get",
	})
}
func demoPost(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "demo post",
	})
}
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	v1 := r.Group("/v1")
	{
		v1.GET("/DemoGet", demoGet)
		v1.POST("/DemoPost", demoPost)
	}
	v2 := r.Group("/v2")
	{
		v2.GET("/DemoGet", demoGet)
		v2.POST("/DemoPost", demoPost)
	}
	r.Run() // 监听端口,默认为0.0.0.0:8080
}
运行程序,访问http://localhost:8080/v2/DemoGet,返回预期结果

HTML渲染
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func main() {
	router := gin.Default()
	router.LoadHTMLGlob("templates/*")
	// 可以通过下面LoadHTMLFiles加载指定的文件
	//router.LoadHTMLFiles("templates/index.html", "templates/index1.html")
	router.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"content": "my website begin",
		})
	})
	router.Run(":8080")
}
运行程序,访问http://localhost:8080/index,返回预期结果

设置和获取Cookie
package main
import (
	"fmt"
	"github.com/gin-gonic/gin"
)
func main() {
	router := gin.Default()
	router.GET("/cookie", func(c *gin.Context) {
		cookie, err := c.Cookie("gin_cookie")
		if err != nil {
			cookie = "NotSet"
			c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
		}
		fmt.Printf("Cookie value: %s \n", cookie)
	})
	router.Run()
}
运行程序,访问http://localhost:8080/index,可以查看cookie信息

XML、YAML、ProtoBuf渲染
package main
import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/testdata/protoexample"
	"net/http"
)
func main() {
	r := gin.Default()
	r.GET("/someXML", func(c *gin.Context) {
		c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})
	r.GET("/someYAML", func(c *gin.Context) {
		c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})
	r.GET("/someProtoBuf", func(c *gin.Context) {
		reps := []int64{int64(1), int64(2)}
		label := "test"
		data := &protoexample.Test{
			Label: &label,
			Reps:  reps,
		}
		c.ProtoBuf(http.StatusOK, data)
	})
	r.Run(":8080")
}
运行程序,访问http://localhost:8080/someYAML,可以查看渲染内容

使用BasicAuth中间件
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
// 模拟一些私有数据
var secrets = gin.H{
	"jasper": gin.H{"email": "jasper@163.com", "phone": "18821212121"},
	"lili":   gin.H{"email": "lili@163.com", "phone": "18821212122"},
	"sam":    gin.H{"email": "sam@163.com", "phone": "18821212123"},
}
func main() {
	r := gin.Default()
	// 使用gin.BasicAuth()中间件进行分组,实际中这里应该是从数据库中获取,gin.Accounts是map[string]string
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
		"jasper": "123456",
		"austin": "123456",
		"lili":   "123456",
		"sam":    "123456",
	}))
	authorized.GET("/secrets", func(c *gin.Context) {
		// 获取user,它是由BasicAuth中间件设置的
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
		} else {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
		}
	})
	// 监听 0.0.0.0:8080
	r.Run(":8080")
}
运行程序,浏览器上访问http://localhost:8080/admin/secrets,在弹出认证窗口上输入用户名密码如jasper/123456

点击登录按钮后则返回预期响应信息。

静态文件和BootStrap
# getbootstrap官网地址:https://getbootstrap.com
# 下载最新版本5.3.0bootstrap
wget https://github.com/twbs/bootstrap/releases/download/v5.3.0/bootstrap-5.3.0-dist.zip
解压目录下css和js目录拷贝到工程目录下,这里将css和js目录放在根目录下的assets目录下,然后创建main.go
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func index(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", nil)
}
func main() {
	// 创建一个带有默认中间件(logger and recovery (crash-free) 中间件)的gin.Engine
	r := gin.Default()
	// 配置静态资源
	r.Static("/assets", "assets")
	r.Static("/favicon.ico", "assets/favicon.ico")
	// 配置路由映射
	r.GET("/index", index)
	//加载html模版文件
	r.LoadHTMLGlob("templates/*")
	r.Run() // 监听端口,默认为0.0.0.0:8080
}
这里从getbootstrap官网上找一个组件放到html文件中

在templates目录下创建index.html模版文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="../assets/css/bootstrap.min.css">
    <script src="../assets/js/bootstrap.min.js"></script>
    <title>静态资源及BootStrap示例</title>
</head>
<body>
<h1>
    <button type="button" class="btn btn-primary">Primary</button>
    <button type="button" class="btn btn-secondary">Secondary</button>
    <button type="button" class="btn btn-success">Success</button>
    <button type="button" class="btn btn-danger">Danger</button>
    <button type="button" class="btn btn-warning">Warning</button>
    <button type="button" class="btn btn-info">Info</button>
    <button type="button" class="btn btn-light">Light</button>
    <button type="button" class="btn btn-dark">Dark</button>
    <button type="button" class="btn btn-link">Link</button>
</h1>
</body>
</html>
上面在html配置link和script时前面加了../,这样才可以在IDE环境中定位到,总体目录结构如下

运行程序,访问http://localhost:8080/index ,已经成功加载到静态资源

使用Session
关于Session的可以使用第三方库的方式,在https://pkg.go.dev/上搜索go session,选择第一个github.com/gin-contrib/sessions,其支持多后端会话管理的Gin中间件包括cookie-based、Redis、memcached、MongoDB、GORM、memstore、PostgreSQL。
# 下载并安装
go get github.com/gin-contrib/sessions
# 在代码中导入
import "github.com/gin-contrib/sessions"
package main
import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	store := cookie.NewStore([]byte("secret"))
	r.Use(sessions.Sessions("mysession", store))
	r.GET("/session", func(c *gin.Context) {
		session := sessions.Default(c)
		if session.Get("mykey") != "myvalue" {
			session.Set("mykey", "myvalue")
			session.Save()
		}
		c.JSON(200, gin.H{"mykey": session.Get("mykey")})
	})
	r.Run(":8080")
}
运行程序,访问http://localhost:8080/session ,成功返回预期结果

上面示例使用的是cookie-based后端存储方式,下面则演示redis作为后端存储示例
package main
import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/redis"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	store, _ := redis.NewStore(10, "tcp", "localhost:6379", "123456", []byte("secret"))
	r.Use(sessions.Sessions("myredissession", store))
	r.GET("/incr", func(c *gin.Context) {
		session := sessions.Default(c)
		var count int
		v := session.Get("mycount")
		if v == nil {
			count = 0
		} else {
			count = v.(int)
			count++
		}
		session.Set("mycount", count)
		session.Save()
		c.JSON(200, gin.H{"mycount": count})
	})
	r.Run(":8080")
}
项目目录下命令行执行go mod tidy,然后多次访问http://localhost:8080/incr,可以看到mycount一直在自增

写入日志文件
package main
import (
	"github.com/gin-gonic/gin"
	"io"
	"os"
)
func main() {
	// 禁用控制台颜色,在将日志写入文件时不需要控制台颜色。
	gin.DisableConsoleColor()
	// 记录到文件
	f, _ := os.Create("my_app.log")
	gin.DefaultWriter = io.MultiWriter(f)
	// 如果需要同时将日志写入文件和控制台,请使用以下代码
	// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
	router := gin.Default()
	router.GET("/hello", func(c *gin.Context) {
		c.String(200, "welcome to go world!")
	})
	router.Run(":8080")
}
启动程序,多次访问http://localhost:8080/hello,可以看到在当前目录下生成了my_app.log文件,用户可以结合日志需要比如使用logrus和file-rotatelogs库实现。

原理
核心执行流程
Gin整体流程还是比较简单的,从上面使用代码示例也可以看到基础执行流程大致如下:
- 创建并初始化Engine对象
- 注册middleware(中间件)
- 注册路由及处理函数
- 服务端口监听
主要设计核心方法流程
- gin.Default():gin的初始化方法,目的是为了创建整个引擎,并初始化相关参数如RouterGroup、pool等。
使用gin框架开发时一般情况下使用默认的engine即可,因为相对于直接使用gin.New()创建Engine对象,它只是多注册了两个中间件。
func Default() *Engine {
	debugPrintWARNINGDefault()
    // 创建引擎
	engine := New()
    // 默认使用Logger和Recovery
	engine.Use(Logger(), Recovery())
	return engine
}

- router.Use():使用中间件的方法,将请求过程中需要调用的中间件放入到HandlersChain,这个是一个数组,比如在请求前后需要加入通用方法如鉴权、自定义日志格式等。
// Use将全局中间件附加到路由器上。也就是说,通过Use()附加的中间件将是包含在每个请求(甚至404、405、静态文件)的处理程序链中,例如记录器或错误管理中间件的正确位置。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}
// 将中间件添加到路由组中
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

- router.GET():构建url和具体处理请求的handle的关系了,其实目标很明确,就是要将这组关系存入到最终的trees中去。
// 链接group.handle快捷方式
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")
	assert1(len(handlers) > 0, "there must be at least one handler")
	debugPrintRoute(method, path, handlers)
	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)
	// Update maxParams
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
		engine.maxParams = paramsCount
	}
	if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
		engine.maxSections = sectionsCount
	}
}

- router.Run():内部流程主要是调用golang中net/http包下的方法监听服务端口。
// Run将路由器附加到http上,服务器并开始监听和服务HTTP请求;也即是http.ListenAndServe快捷方式,对于除非发生错误,否则此方法将无限期地阻塞调用例程
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
}

- ServeHTTP:接收请求请求的处理在gin.go中的ServeHTTP,其处理http.Handler接口。其根据请求的url和method找到对应的handle去处理,通过数查找去。同时利用context进行参数传递,用c.Next进行递归遍历中间件的调用,handle是一个链式过程。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	engine.handleHTTPRequest(c)
	engine.pool.Put(c)
}

核心数据结构
在New()创建*Engine的方法中可以看到初始化重要信息,

其核心组成的重要结构如下

Engine是一个总的引擎,保存了各个组件的信息 ,其他组件信息如下:
- RouterGroup是一个路由组,路由管理相关,保存了路由信息 。路由组的目的是为了实现配置的复用,相关的请求使用一组单独的middleware;gin.Engine对象本身就是一个路由组。
type RouterGroup struct {
   // 路由组处理函数链,其下路由的函数链将结合路由组和自身的函数组成最终的函数链
   Handlers HandlersChain
   // 路由组的基地址,一般是其下路由的公共地址
   basePath string
   // 路由组所属的Engine,这里构成了双向引用
   engine   *Engine
   // 该路由组是否位于根节点,基于RouterGroup.Group创建路由组时此属性为false
   root     bool
}
在RouterGroup数据结构有一个非常重要的成员字段HandlersChain(处理器链 ),用于收集该路由组下注册的middleware函数。在运行时,会按顺序执行HandlersChain中的注册的函数。
// HandlersChain 定义为一个HandlerFunc切片.
type HandlersChain []HandlerFunc
// HandlerFunc 定义gin中间件使用的处理程序作为返回值
type HandlerFunc func(*Context)
- 路由树数组trees:trees是一棵树,保存了url与handle的映射关系 ,粗暴一点可以简单理解为key就是url字符串,value对应的[]HandleFunc;标准库本身的路由是不区分请求方法的,也就是说注册一个路由后,GET、POST都能匹配到该路由,而还需要在同一个路由在不同的请求方法下,由不同的逻辑进行处理。其实就是通过路由树实现的,gin的针对每个请求方法都有一棵路由树。Gin利用基于Radix Tree基数树思想通过优秀的数据结构和算法设计达到高性能目标。其核心实现是在gin的tree.go源码文件中。 - Radix Tree是一种基于 Trie(字典树)的数据结构,旨在解决字符串搜索和匹配的问题。它最早由 Fredkin 在 1960 年提出,并在之后被广泛应用于各种应用领域。其最大的特点就是在 Trie 的基础上,加入了路径压缩的逻辑,通过合并前缀的方式大大的减少了 Trie 中的节点冗余问题,不仅提高了查询效率,还减少了存储空间的使用。
 
- context对象池:engine中的pool用于复用Context,gin.Context是gin框架暴露给开发的另一个核心对象,可以通过该对象获取请求信息,业务处理的结果也是通过该对象写回客户端的。为了实现context对象的复用,gin基于sync.Pool实现了对象池。由于请求多会产生很多数量的context,利用pool来重复利用对象,从而减少内存的分配也提高了效率。 
- Context:包含了Request,Writer等信息,用于request中传递值。 
- 本人博客网站IT小神 www.itxiaoshen.com 
云原生时代Go最受欢迎Web开源框架Gin原理与实战的更多相关文章
- 云原生时代之Kubernetes容器编排初步探索及部署、使用实战-v1.22
		概述 **本人博客网站 **IT小神 www.itxiaoshen.com Kubernetes官网地址 https://kubernetes.io Kubernetes GitHub源码地址 htt ... 
- 云原生时代,Java的危与机(周志明)
		说明 本篇文章是转载自周志明老师的文章,链接地址:https://www.infoq.cn/article/RQfWw2R2ZpYQiOlc1WBE 今天,25 岁的 Java 仍然是最具有统治力的编 ... 
- 云原生时代的Java
		原文链接(作者:周志明):https://time.geekbang.org/column/article/321185 公开课链接:https://time.geekbang.org/opencou ... 
- 云原生之旅 - 9)云原生时代网关的后起之秀Envoy Proxy 和基于Envoy 的 Emissary Ingress
		前言 前一篇文章讲述了基于Nginx代理的Kuberenetes Ingress Nginx[云原生时代的网关 Ingress Nginx]这次给大家介绍下基于Envoy的 Emissary Ingr ... 
- 阿里云弹性容器实例产品 ECI ——云原生时代的基础设施
		阿里云弹性容器实例产品 ECI ——云原生时代的基础设施 1. 什么是 ECI 弹性容器实例 ECI (Elastic Container Instance) 是阿里云在云原生时代为用户提供的基础计算 ... 
- 进击的 Java ,云原生时代的蜕变
		作者| 易立 阿里云资深技术专家 导读:云原生时代的来临,与Java 开发者到底有什么联系?有人说,云原生压根不是为了 Java 存在的.然而,本文的作者却认为云原生时代,Java 依然可以胜任&qu ... 
- 进击的.NET 在云原生时代的蜕变
		你一定看过这篇文章 <进击的 Java ,云原生时代的蜕变>, 本篇文章的灵感来自于这篇文章.明天就将正式发布.NET Core 3.0, 所以写下这篇文章让大家全面认识.NET Cor ... 
- 开放下载 | 《Knative 云原生应用开发指南》开启云原生时代 Serverless 之门
		点击下载<Knative 云原生应用开发指南> 自 2018 年 Knative 项目开源后,就得到了广大开发者的密切关注.Knative 在 Kubernetes 之上提供了一套完整的应 ... 
- [转帖]从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑?
		从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑? 2019-10-08 10:26:28 阿里云云栖社区 阅读数 54 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权 ... 
- .NET 在云原生时代的蜕变,让我在云时代脱颖而出
		.NET 生态系统是一个不断变化的生态圈,我相信它正在朝着一个伟大的方向发展.有了开源和跨平台这两个关键优先事项,我们就可以放心了.云原生对应用运行时的不同需求,说明一个.NET Core 在云原生时 ... 
随机推荐
- Zab(Zookeeper Atomic Broadcast)协议
			更多内容,前往IT-BLOG 一.什么是 Zab协议 Zab( Zookeeper Atomic Broadcast:Zookeeper原子广播)Zookeeper 通过 Zab 协议保证分布式事务的 ... 
- flex布局相关属性记录
			<template> <div class="about"> <h3>flex相关的属性</h3> <div class=&q ... 
- JMM内存模型
			● 说说JVM的主要组成部分以及作用? 类加载器.运行时数据区.执行引擎.本地库接口 类加载器子系统 它主要功能是处理类的动态加载,还有链接,并且在第一次引用类时进行初始化. Loading - 加载 ... 
- 2020寒假学习笔记13------Python基础语法学习(二)
			同一运算符 同一运算符用于比较两个对象的存储单元,实际比较的是对象的地址. 运算符 描述 is is 是判断两个标识符是不是引用同一个对象 is not is not 是判断两个标识符是不是引用 ... 
- EasyRelation发布,简单强大的数据关联框架
			当开发人员需要进行关联查询时,往往需要编写大量的冗余代码来处理数据之间的关系.这不仅浪费时间和精力,还会影响项目的可维护性和可扩展性. EasyRelation 是一个简单.高效的自动关联数据框架,可 ... 
- AI开发实践:关于停车场中车辆识别与跟踪
			摘要:本案例我们使用FairMOT进行车辆检测与跟踪.yolov5进行车牌检测.crnn进行车牌识别,在停车场入口.出口.停车位对车辆进行跟踪与车牌识别,无论停车场路线多复杂,小车在你掌控之中! 本文 ... 
- 【入门排坑】Windows之间使用OpenSSH的ssh免密登录,排坑
			安装 安装OpenSSH 需要安装OpenSSH客户端和服务器,win10自带客户端,我们安装服务器即可. 设置 -- 应用 -- 可选功能 -- 添加 -- 添加 OpenSSH 服务器 配置 公钥 ... 
- [MyBatis]MyBatis问题及解决方案记录
			1字节的UTF-8序列的字节1无效 - CSDN 手动将<?xml version="1.0" encoding="UTF-8"?>中的UTF-8更 ... 
- Nordic芯片烧录指南
			本文讲介绍Nordic系列芯片的烧录方式 一.准备工作 1.硬件 首先需要准备一块Nordic的DK或者Jlink,但是需要注意的是x宝购买的盗版Jlink因为没有license,用一段时间可能会被锁 ... 
- MySQL主从复制原理剖析与应用实践
			vivo 互联网服务器团队- Shang Yongxing MySQL Replication(主从复制)是指数据变化可以从一个MySQL Server被复制到另一个或多个MySQL Server上, ... 
