云原生时代Go最受欢迎Web开源框架Gin原理与实战

news2025/1/20 20:14:07

文章目录

  • 概述
    • 定义
    • 特点
    • 概览导图
  • 使用
    • 快速入门
    • HTTP 方法使用
    • 参数获取
    • 参数绑定
    • 自定义日志输出
    • 自定义中间件
    • 路由组
    • HTML渲染
    • 设置和获取Cookie
    • XML、YAML、ProtoBuf渲染
    • 使用BasicAuth中间件
    • 静态文件和BootStrap
    • 使用Session
    • 写入日志文件
  • 原理
    • 核心执行流程
    • 核心数据结构

概述

定义

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。
  • 可扩展的:非常简单通过自定义创建新的中间件实现扩展功能。

概览导图

image-20230607105024396

使用

快速入门

前置环境:需要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

image-20230607161226428

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,返回预期结果

image-20230607152221954

参数获取

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,返回预期结果

image-20230607154935315

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

image-20230607155632802

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

image-20230607182046122

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

image-20230607162139176

参数绑定

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输出

image-20230607164536320

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

image-20230607165728507

自定义日志输出

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,查看运行的控制台日志可以看到自定义日志输出

image-20230607172843280

自定义中间件

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结果

image-20230607173605484

路由组

路由组可以多级嵌套,实现细粒度控制

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,返回预期结果

image-20230607175721946

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,返回预期结果

image-20230607181317023

设置和获取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信息

image-20230607183352696

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,可以查看渲染内容

image-20230607183758358

使用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

image-20230608093003975

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

image-20230608092556375

静态文件和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文件中

image-20230608101613987

在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环境中定位到,总体目录结构如下

image-20230608101253555

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

image-20230608101737098

使用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 ,成功返回预期结果

image-20230608105348115

上面示例使用的是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一直在自增

image-20230608110332310

写入日志文件

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库实现。

image-20230608135813417

原理

核心执行流程

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
}

image-20230608155855768

  • 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()
}

image-20230608165518013

  • 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
	}
}

image-20230608171339533

  • 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
}

image-20230608174116829

  • 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)
}

image-20230608174812538

核心数据结构

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

image-20230608153210397

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

image-20230608155626400

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ABAQUS文档笔记】缩减积分-剪切闭锁-沙漏问题-非协调模式-混杂单元

接上一篇博客 来自ABAQUS DOCUMENT/GETTING STARTED WITH ABAQUS/CAE /USING CONTINUUM ELEMENTS 整理了典型实体单元类型的优缺点&#xff0c;和问题 1. 单元公式和积分 1.1 full integration —— shear lock “完全积分”是指当单元具有规则形状时&#xff0c;对单元刚度…

【Vue】学习笔记-基本路由 多级路由

相关理解 vue-router 的理解 vue的一个插件库&#xff0c;专门用来实现SPA应用 对SPA应用的理解 单页web应用(single page web application ,SPA)整个应用只有一个完整的页面点击页面中的导航链接不会刷新页面&#xff0c;只会做页面的局部更新数据需要通过ajax请求获取 …

头歌人工智能学习记录

因为这个实训的顺序不同&#xff0c;所以这里的顺序是个人学习的顺序&#xff0c;可能有些变动 第1关&#xff1a;Sigmoid函数 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 激活函数概述&#xff1b; 线性函数&#xff1b; Sigmoid 函数。 激活函数概述 …

HTTPS协议原理

目录 HTTPS是什么 1. 什么是"加密" 2. 为什么要加密 3. 常⻅的加密⽅式 对称加密 ⾮对称加密 4. 数据摘要&&数据指纹 5. 数字签名 HTTPS的⼯作过程 ⽅案1- 只使⽤对称加密 ⽅案2 - 只使⽤⾮对称加密 ⽅案3 - 双⽅都使⽤⾮对称加密 ⽅案4 - ⾮对…

2023年学自动化测试?Python 还是 Java?“我“上车了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化测试&#…

2023年牛客网最新的Java面试经历整理(一次性查缺补漏个够)

学的人越多&#xff0c;越容易供大于求&#xff0c;越容易卷&#xff0c;要求越高&#xff01; 当前 Java 程序员的群体比较庞大&#xff0c;Java 本身语言是全场景编程语言&#xff0c;所以不少大厂都要求 Java 程序员具有全栈开发能力和多场景开发能力&#xff0c;还有就是 …

Sui主网上线后的生态发展

Sui主网上线一个月有余&#xff0c;这段时间&#xff0c;Sui网络进行多次迭代更新&#xff0c;生态正在不断稳步发展&#xff0c;社区也日益焕发出生机和活力。为吸引更多的项目或开发者前来构建&#xff0c;Sui基金会推出了多项资助计划以及黑客松&#xff0c;进一步助力生态持…

【高级篇】多级缓存

文章目录 多级缓存1.什么是多级缓存2.JVM进程缓存2.1.导入案例案例导入说明1.安装MySQL1.1.准备目录1.2.运行命令1.3.修改配置1.4.重启 2.导入SQL3.导入Demo工程3.1.分页查询商品3.2.新增商品3.3.修改商品3.4.修改库存3.5.删除商品3.6.根据id查询商品3.7.根据id查询库存3.8.启动…

动态规划-背包问题(二)

动态规划-背包问题&#xff08;二&#xff09; 1 描述2 样例2.1 样例 1&#xff1a;2.2 样例 2&#xff1a;2.3 挑战 3 算法解题思路以及实现方法3.1 算法解题思路3.1.1 确定状态3.1.2 转移方程3.1.3 初始条件和边界情况3.1.4 计算顺序 3.2 空间复杂度为O(MN)的算法实现3.2.1 j…

卡尔曼滤波与组合导航原理(十一)区间平滑:前向滤波、反向滤波、双向区间平滑、RTS平滑

最优预测、估计与平滑之间的关系&#xff1a; 三种平滑方式&#xff1a; 函数模型和随机模型 { X k Φ k / k − 1 X k − 1 Γ k − 1 W k − 1 Z k H k X k V k { E [ W k ] 0 , E [ W k W j T ] Q k δ k j E [ V k ] 0 , E [ V k V j T ] R k δ k j E [ W k V j …

Kubernetes DashBoard

Kubernetes DashBoard 为了便于用户操作&#xff0c;k8s开发了基于Web的用户界面。方便用户容器化应用&#xff0c;还可以监控应用状态&#xff0c;执行故障排除和管理资源。 &#x1f4ca;部署DashBoard 下载yaml&#xff0c;运行DashBoard #下载yaml [rootmaster ~]# wget …

chatgpt赋能python:Python图片裁剪:让您的图片变得更完美

Python 图片裁剪&#xff1a;让您的图片变得更完美 无论您是一名摄影师、设计师、或是一名开发者&#xff0c;一张完美的图片是至关重要的。然而&#xff0c;有时候您拍摄的图片或者设计的图形可能有一些缺陷&#xff0c;例如画面不够清晰、不需要的元素等等&#xff0c;这时候…

数据结构与算法·第7章【图】

图 大部分定义都在离散数学II中学过了&#xff0c;所以对于已知或常见的我不多赘述 弧或边带权的图分别称作有向网或无向网。若边或弧的个数 e<nlogn&#xff0c;则称作稀疏图&#xff0c;否则称作稠密图。对有向图&#xff0c;若任意两个顶点之间都存在一条有向路径&…

【深蓝学院】手写VIO第3章--基于优化的 IMU 与视觉信息融合--笔记

0. 内容 1. 基于BA的VIO融合 优化的方法学会之后&#xff0c;滤波的方法也就会了。 具体的求解BA问题参考的是SBA的论文&#xff0c;使用的是LM算法&#xff08;里面有个关于权重μ的计算方法&#xff0c;不同人的实现可能不一样&#xff0c;这些都是实现细节&#xff09; …

全新 FinClip Studio 现已上线

FinClip IDE &#xff08;FinClip Integrated Development Environment&#xff0c;简称为 FIDE&#xff09;是面向开发者推出的「小程序桌面端集成开发环境」。自 2021 年起就一直陪伴来自不同平台的小程序开发者完成小程序开发、调试、预览、上传等各类功能。 随着 FinClip …

【MySQL】初识数据库

数据库基础知识 一、什么是数据库二、mysql与mysqld三、服务器、数据库、表关系四、数据库的连接、创建与使用4.1 安装地址4.2 连接服务器4.3 数据库的基本使用4.4 数据逻辑存储 五、MySQL架构六、SQL分类七、存储引擎 一、什么是数据库 存储数据用文件就可以了&#xff0c;为…

一文即可了解!Web测试(性能测试 / 界面测试 / 兼容性测试 / 安全性测试)

目录 前言&#xff1a; 一、Web性能测试&#xff1a;&#xff08;压力测试、负载测试、连接速度测试&#xff09; 二、Web界面测试&#xff1a;&#xff08;导航测试、图形测试、内容测试、整体界面测试&#xff09; 三、Web兼容性测试&#xff1a;&#xff08;平台&#x…

Linux搭建Java环境——安装MySQL5.7(CentOS7.6)

一、使用Xftp上传MySQL安装包&#xff0c;并解压 这里可以直接使用安装包也可以使用命令直接在Xshell运行 wget http://dev.mysql.com/get/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar 当然要提前创建好文件夹/opt/mysql&#xff0c;并cd进去&#xff0c;运行tar -xvf mysql-…

通义千问写的高考作文你觉得怎么样?

目录 一、全国卷-全国甲卷 二、北京卷 三、上海卷 今天带大家使用通义千问来体验一下2023年高考作文&#xff0c;大家一起来一睹为快吧&#xff01; 一、全国卷-全国甲卷 阅读下面的材料&#xff0c;根据要求写作。&#xff08;60分&#xff09; 人们因技术发展得以更好地掌控…

计算机网络管理-使用SNMPc开展网管活动

一、实验目的 全面学习SNMPc网络管理软件业务服务监控功能&#xff0c;了解如何使用网管软件从事网络管理工作 二、实验内容与设计思想 1&#xff09;操作映射数据库。 2&#xff09;查看管理对象的MIB数据。 3&#xff09;创建、保存长期统计数据&#xff08;要求一定时长…