Gin 源码阅读随笔

294 阅读10分钟

Gin 源码阅读随笔

介绍

Gin 是一个使用 Go 编程语言的 Web 框架。它是一个类似于 Martini 但拥有更好性能的 API 框架。如果你需要一个高性能的 Web 框架,那么使用 Gin 是一个不错的选择。Gin 框架使用基于 Radix 树的路由匹配,它可以支持路由组、中间件、JSON 验证、文件上传、Swagger 等功能。

简单阅读学习下 Gin 源码。

GitHub: gin-gonic/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. (github.com)

文档: 文档 | Gin Web Framework (gin-gonic.com)

阅读版本: release v1.8.2

Demo

func main() {
    router := gin.New()
    
    router.Use(gin.Logger(), gin.Recovery())
    
    router.GET("/demo", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "code" : 100,
            "message" : "ok",
	    "data": "hello",
        })
    })

    if err := router.Run(); err != nil {
        panic(err)
    }
}

整体结构

Untitled.png

Engine 是一个总的引擎,保存了各个组件的信息;

RouterGroup 是一个路由组,保存了路由信息;

Trees 是一棵紧缩前缀树,保存了 url 与 handle 的映射关系;

Pool 用于复用 Context;

Context 用于 request 中传递值。

Engine

gin.New()会返回一个EngineEngine是 gin 框架最重要的数据结构,也是框架的入口。Engine的成员有很多,其实也只需要了解即可,使用gin.New()初始化的即可。

type Engine struct {
	// 路由组,存储所有中间件和请求路径,详细看 routergroup.go
	RouterGroup

	// 重定向跟踪间隙,对后缀为 / 的路由是否开启重定向请求
	// 例如 /foo/ 重定向到 /foo 去
	// default true
	RedirectTrailingSlash bool

	// 重定向修复路由,尝试修复当前的请求地址
	// 1.删除首位多余元素 (../ or //)
	// 2.然后对新的路径进行不区分大小写的查找
	// 3.如果找到对应的 handler 就重定向并返回 301 或 307
	// 例如 /FOO 和 /..//Foo 可能会被重定向到 /foo 上
	// default false
	RedirectFixedPath bool

	// 不允许处理方法,检查当前路径是否允许使用其他请求方法来路由
	// 可以用来代替掉 404 页面
	// default false
	HandleMethodNotAllowed bool

	// 是否转发客户端 IP
	// default true
	ForwardedByClientIP bool

	// 已弃用 不理它
	AppEngine bool

	// 如果启动,将使用 url.RawPath 查找参数
	// default false
	UseRawPath bool

	// 为 true 则路径值将被取消转义
	// 如果 UseRawPath 为 false,则 UnescapePathValues 实际上为 true,
	// 作为 url.Path 将被使用,它已经被转义了
	UnescapePathValues bool

	// 移除额外的反斜线
	// default false
	RemoveExtraSlash bool

	// 从以下情况获取客户端 IP
	// ForwardedByClientIP 为 true, context.Request.RemoteAddr 有值
	// default []string{"X-Forwarded-For","X-Real-IP"}
	RemoteIPHeaders []string

	// 如果设置为 gin.Platform* 的常量,则信任该平台设置的标头
	// default "X-Appengine-Remote-Addr"
	TrustedPlatform string

	// 赋予 http.Request 的 ParseMultipartForm 参数的值
	// default 32 << 20 // 32 MB
	MaxMultipartMemory int64

	// 允许h2c支持
	UseH2C bool

	ContextWithFallback bool

	// 代表用于 HTML 模板呈现的一组左右定界符
	delims render.Delims

	// 设置在 Context.SecureJSON 的 json 前缀
	secureJSONPrefix string

	// 返回 HTMLRender 接口
	HTMLRender render.HTMLRender

	// 从名称到函数的映射
	FuncMap template.FuncMap

	// 复制一份全局的 handlers,加上 NoRoute 处理方法
	allNoRoute HandlersChain

	// 复制一份全局的 handlers,加上 NoMethod 处理方法
	allNoMethod HandlersChain

	// 为 NoRoute 添加处理程序,默认返回 404
	noRoute HandlersChain

	// 为 NoMethod 添加处理程序,默认返回 404
	noMethod HandlersChain

	// 上下文池,优化处理 http 请求时的性能
	pool sync.Pool

	// 存储路由和 handle 方法的映射,采用Radix树结构
	trees methodTrees

	// 分配 context 时,初始化参数 Params 的切片长度
	maxParams uint16

	maxSections uint16

	trustedProxies []string

	// IP 网络地址指针切片
	trustedCIDRs []*net.IPNet
}
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
		TrustedPlatform:        defaultPlatform,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
		trustedProxies:         []string{"0.0.0.0/0", "::/0"},
		trustedCIDRs:           defaultTrustedCIDRs,
	}

	engine.RouterGroup.engine = engine
	engine.pool.New = func() any {
		return engine.allocateContext(engine.maxParams)
	}
	return engine
}

另外,也可以使用gin.Default()来初始化,它不仅调用New()方法,还添加了两个全局中间件,engine.Use(Logger(), Recovery())Logger用于打印日志,Recovery用于防止 panic。

func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

Engine.Use()就是用来绑定全局中间件的。

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

func (engine *Engine) rebuild404Handlers() {
	engine.allNoRoute = engine.combineHandlers(engine.noRoute)
}

func (engine *Engine) rebuild405Handlers() {
	engine.allNoMethod = engine.combineHandlers(engine.noMethod)
}

Engine.addRouter()将路由方法、路径、处理方法切片存入路由树中,路由组的GET方法、POST方法等也都需要调用到这个方法。

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

Engine.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.")
	}

	// 处理addr参数 长度为0就默认8080端口,为1就是指定那个端口,否则panic
	address := resolveAddress(addr)

	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine.Handler())
	return
}

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

Engine.ServeHTTP处理 HTTP 请求。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 从上下文池里取出一个,重置 http.ResponseWriter 和 *http.Request,其他 Context 的参数也都重置
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

func (engine *Engine) handleHTTPRequest(c *Context) {
	// GET POST ...
	httpMethod := c.Request.Method

	// url
	rPath := c.Request.URL.Path
	unescape := false

	// 使用 url.RawPath 查找参数
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}

	// 移除额外反斜线
	if engine.RemoveExtraSlash {
		rPath = cleanPath(rPath)
	}

	// 遍历路由树中对应的 httpMethod 的节点
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// 找到 rPath 对应的路由节点
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil {
			c.Params = *value.params // Params 赋值
		}
		if value.handlers != nil {
			c.handlers = value.handlers // handlers 赋值
			c.fullPath = value.fullPath // fullPath 赋值
			c.Next()                    // 执行路由对应的处理方法
			c.writermem.WriteHeaderNow()
			return
		}

		// 出现不规则的自定义路由时并且有类似的路由存在 重定向处理
		if httpMethod != http.MethodConnect && rPath != "/" {
			if value.tsr && engine.RedirectTrailingSlash {
				redirectTrailingSlash(c)
				return
			}
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
				return
			}
		}
		break
	}

	// 检查是否允许使用其他方法
	if engine.HandleMethodNotAllowed {
		for _, tree := range engine.trees {
			if tree.method == httpMethod {
				continue
			}

			// 检查到该请求方式没有请求被路由时,返回不允许的方法(http code = 405)
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	// 没有路由返回 404
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

RouterGroup

// IRouter 定义了所有路由处理器接口,包括单个和组处理器
type IRouter interface {
	IRoutes
	Group(string, ...HandlerFunc) *RouterGroup
}

// IRoutes 定义了所有路由处理器接口
type IRoutes interface {
	Use(...HandlerFunc) IRoutes

	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes

	StaticFile(string, string) IRoutes
	StaticFileFS(string, string, http.FileSystem) IRoutes
	Static(string, string) IRoutes
	StaticFS(string, http.FileSystem) IRoutes
}

// 路由器组在内部用于配置路由器,
// 路由器组与前缀和处理程序数组(中间件)相关联
type RouterGroup struct {
	Handlers HandlersChain // HandlerFunc 切片
	basePath string        // 路径
	engine   *Engine       // 引擎
	root     bool          // 是否为根
}

RouterGroup.Use()注册局部中间件。

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

RouterGroup.Group()注册路由组中间件。

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		// group.combineHandlers(handlers) 会复制一份全局中间件
		// group.calculateAbsolutePath(relativePath) 会连接路径
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

RouterGroup.handle绑定方法+路径+handlers,这个方法不对外暴露。

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)
	// 调用 Engine.addRoute
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

RouterGroup.Handle,调用内部的RouterGroup.handle,其实用不到这个,一般用RouterGroup.GETRouterGroup.POST等。

func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
	if matched := regEnLetter.MatchString(httpMethod); !matched {
		panic("http method " + httpMethod + " is not valid")
	}
	return group.handle(httpMethod, relativePath, handlers)
}
// POST is a shortcut for router.Handle("POST", path, handlers).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPost, relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

Tree

gin 的 紧缩前缀树实现源码比较复杂,了解简单概念就可以了。

比如有以下几个路由:

r.GET("/", handle1)
r.GET("/demo", handle2)
r.GET("/demo/:id", handle3)
r.GET("/demo/:name", handle4)

那么紧缩前缀树就是下面这样:

Untitled 1.png

node 是树的节点。

type nodeType uint8

const (
	static nodeType = iota // default,静态节点,普通匹配(/user)
	root                   // 根节点 (/)
	param                  // 参数节点(/user/:id)
	catchAll               // 通用匹配,匹配任意参数(*user)
)

type node struct {
	path      string        // 节点路径
	indices   string        // 节点关键字,分裂的分支的第一个字符
	wildChild bool          // 是否为独立节点
	nType     nodeType      // 节点类型
	priority  uint32        // 节点的优先级,注册的函数数量,函数越多优先级越高
	children  []*node       // 子节点
	handlers  HandlersChain // 节点路由函数
	fullPath  string        // 路由
}

node.addRoute()会把路径和处理方法切片添加到 node,这也是Engine.addRoute()会调用的方法。

func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++

	// Empty tree
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(path, fullPath, handlers)
		n.nType = root
		return
	}

	parentFullPathIndex := 0

walk:
	for {
		// Find the longest common prefix.
		// This also implies that the common prefix contains no ':' or '*'
		// since the existing key can't contain those chars.
		i := longestCommonPrefix(path, n.path)

		// Split edge
		if i < len(n.path) {
			child := node{
				path:      n.path[i:],
				wildChild: n.wildChild,
				nType:     static,
				indices:   n.indices,
				children:  n.children,
				handlers:  n.handlers,
				priority:  n.priority - 1,
				fullPath:  n.fullPath,
			}

			n.children = []*node{&child}
			// []byte for proper unicode char conversion, see #65
			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
			n.path = path[:i]
			n.handlers = nil
			n.wildChild = false
			n.fullPath = fullPath[:parentFullPathIndex+i]
		}

		// Make new node a child of this node
		if i < len(path) {
			path = path[i:]
			c := path[0]

			// '/' after param
			if n.nType == param && c == '/' && len(n.children) == 1 {
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++
				continue walk
			}

			// Check if a child with the next path byte exists
			for i, max := 0, len(n.indices); i < max; i++ {
				if c == n.indices[i] {
					parentFullPathIndex += len(n.path)
					i = n.incrementChildPrio(i)
					n = n.children[i]
					continue walk
				}
			}

			// Otherwise insert it
			if c != ':' && c != '*' && n.nType != catchAll {
				// []byte for proper unicode char conversion, see #65
				n.indices += bytesconv.BytesToString([]byte{c})
				child := &node{
					fullPath: fullPath,
				}
				n.addChild(child)
				n.incrementChildPrio(len(n.indices) - 1)
				n = child
			} else if n.wildChild {
				// inserting a wildcard node, need to check if it conflicts with the existing wildcard
				n = n.children[len(n.children)-1]
				n.priority++

				// Check if the wildcard matches
				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
					// Adding a child to a catchAll is not possible
					n.nType != catchAll &&
					// Check for longer wildcard, e.g. :name and :names
					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
					continue walk
				}

				// Wildcard conflict
				pathSeg := path
				if n.nType != catchAll {
					pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
				}
				prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
				panic("'" + pathSeg +
					"' in new path '" + fullPath +
					"' conflicts with existing wildcard '" + n.path +
					"' in existing prefix '" + prefix +
					"'")
			}

			n.insertChild(path, fullPath, handlers)
			return
		}

		// Otherwise add handle to current node
		if n.handlers != nil {
			panic("handlers are already registered for path '" + fullPath + "'")
		}
		n.handlers = handlers
		n.fullPath = fullPath
		return
	}
}

Context

type Context struct {
	// 处理响应
	writermem responseWriter

	// http 请求
	Request *http.Request

	// http 响应
	Writer ResponseWriter

	// url 参数,由 key 和 value 组成
	Params Params

	// handlerFunc 数组
	handlers HandlersChain

	// handlerFunc 数组索引
	index int8

	// 请求地址
	fullPath string

	// 引擎
	engine *Engine

	// url 参数的切片
	params *Params

	skippedNodes *[]skippedNode

	// 读写锁,用来保护 Keys
	mu sync.RWMutex

	// 专门针对每个请求上下文的键值对
	Keys map[string]any

	// 使用此上下文的所有处理程序或者中间件附带的错误列表
	Errors errorMsgs

	// 自定义请求接受的内容类型格式
	Accepted []string

	// 使用 url.ParseQuery 从 c.Request.URL.Query() 缓存了参数查询结果
	queryCache url.Values

	// 使用 url.ParseQuery 缓存的 PostForm 包含来自 POST、PATCH 或者 PUT 请求参数
	formCache url.Values

	// 允许服务器定义 cookie 属性,用来防止 CSRF 攻击和用户追踪
	sameSite http.SameSite
}

Context.reset(),重置 Context,在 Pool 里用于 Context 复用。

func (c *Context) reset() {
	c.Writer = &c.writermem
	c.Params = c.Params[:0]
	c.handlers = nil
	c.index = -1

	c.fullPath = ""
	c.Keys = nil
	c.Errors = c.Errors[:0]
	c.Accepted = nil
	c.queryCache = nil
	c.formCache = nil
	c.sameSite = 0
	*c.params = (*c.params)[:0]
	*c.skippedNodes = (*c.skippedNodes)[:0]
}

中间件中常用到流程控制的两个方法:Context.Next()Context.Abort()

// 依次调用处理程序
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

// 阻止后续中间件的执行
func (c *Context) Abort() {
	c.index = abortIndex
}

元数据管理,在上下文中存储键值对,这里只列出 Set 和 Get 方法,还有很多不同类型的相似的方法。

// 在上下文存储键值对,存在 Context.Keys 里
func (c *Context) Set(key string, value any) {
	c.mu.Lock()
	if c.Keys == nil {
		c.Keys = make(map[string]any)
	}

	c.Keys[key] = value
	c.mu.Unlock()
}

// 从 Context.Keys 里取数据
func (c *Context) Get(key string) (value any, exists bool) {
	c.mu.RLock()
	value, exists = c.Keys[key]
	c.mu.RUnlock()
	return
}

有很多获取请求参数的方法。

// Param returns the value of the URL param.
// It is a shortcut for c.Params.ByName(key)
//
//	router.GET("/user/:id", func(c *gin.Context) {
//	    // a GET request to /user/john
//	    id := c.Param("id") // id == "/john"
//	    // a GET request to /user/john/
//	    id := c.Param("id") // id == "/john/"
//	})
// 当路由是这种形式时router.GET("/user/:id", func(c *gin.Context) {}),可以通过 id := c.Param("id") 来获取到 id 的参数值
// 上面英文注释好像错了,应该是不会附加第一个 / 号的
func (c *Context) Param(key string) string {
	return c.Params.ByName(key)
}

// AddParam adds param to context and
// replaces path param key with given value for e2e testing purposes
// Example Route: "/user/:id"
// AddParam("id", 1)
// Result: "/user/1"
// 添加路径的Param
func (c *Context) AddParam(key, value string) {
	c.Params = append(c.Params, Param{Key: key, Value: value})
}

// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
//
//	    GET /path?id=1234&name=Manu&value=
//		   c.Query("id") == "1234"
//		   c.Query("name") == "Manu"
//		   c.Query("value") == ""
//		   c.Query("wtf") == ""
// 获取GET请求的数据,可为空
func (c *Context) Query(key string) (value string) {
	value, _ = c.GetQuery(key)
	return
}

// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
//
//	GET /?name=Manu&lastname=
//	c.DefaultQuery("name", "unknown") == "Manu"
//	c.DefaultQuery("id", "none") == "none"
//	c.DefaultQuery("lastname", "none") == ""
// 获取GET请求的数据,带默认值
func (c *Context) DefaultQuery(key, defaultValue string) string {
	if value, ok := c.GetQuery(key); ok {
		return value
	}
	return defaultValue
}

// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
//
//	GET /?name=Manu&lastname=
//	("Manu", true) == c.GetQuery("name")
//	("", false) == c.GetQuery("id")
//	("", true) == c.GetQuery("lastname")
// 获取GET请求的数据,返回时带 bool
func (c *Context) GetQuery(key string) (string, bool) {
	if values, ok := c.GetQueryArray(key); ok {
		return values[0], ok
	}
	return "", false
}

// QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key.
// 获取GET请求的数据,返回切片(一个key可能有多个value)
func (c *Context) QueryArray(key string) (values []string) {
	values, _ = c.GetQueryArray(key)
	return
}

// 初始化 Context.queryCache,GET请求的参数都在这里面
func (c *Context) initQueryCache() {
	if c.queryCache == nil {
		if c.Request != nil {
			c.queryCache = c.Request.URL.Query()
		} else {
			c.queryCache = url.Values{}
		}
	}
}

// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
// 获取GET请求的数据,返回切片和bool
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
	c.initQueryCache()
	values, ok = c.queryCache[key]
	return
}

// QueryMap returns a map for a given query key.
// 获取GET请求的数据,返回键值对
func (c *Context) QueryMap(key string) (dicts map[string]string) {
	dicts, _ = c.GetQueryMap(key)
	return
}

// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
// 获取GET请求的数据,返回键值对和bool
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
	c.initQueryCache()
	return c.get(c.queryCache, key)
}

// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
// 获取 POST、PATCH 或者 PUT 请求的数据,可为空
func (c *Context) PostForm(key string) (value string) {
	value, _ = c.GetPostForm(key)
	return
}

// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string.
// See: PostForm() and GetPostForm() for further information.
// 获取 POST、PATCH 或者 PUT 请求的数据,带默认值
func (c *Context) DefaultPostForm(key, defaultValue string) string {
	if value, ok := c.GetPostForm(key); ok {
		return value
	}
	return defaultValue
}

// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns ("", false).
// For example, during a PATCH request to update the user's email:
//
//	    email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
//		   email=                  -->  ("", true) := GetPostForm("email") // set email to ""
//	                            -->  ("", false) := GetPostForm("email") // do nothing with email
// 获取 POST、PATCH 或者 PUT 请求的数据,返回带bool
func (c *Context) GetPostForm(key string) (string, bool) {
	if values, ok := c.GetPostFormArray(key); ok {
		return values[0], ok
	}
	return "", false
}

// PostFormArray returns a slice of strings for a given form key.
// The length of the slice depends on the number of params with the given key.
// 获取 POST、PATCH 或者 PUT 请求的数据,返回切片
func (c *Context) PostFormArray(key string) (values []string) {
	values, _ = c.GetPostFormArray(key)
	return
}

// 初始化 Context.formCache,POST、PATCH 或者 PUT 请求的参数都在这里面
func (c *Context) initFormCache() {
	if c.formCache == nil {
		c.formCache = make(url.Values)
		req := c.Request
		if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
			if !errors.Is(err, http.ErrNotMultipart) {
				debugPrint("error on parse multipart form array: %v", err)
			}
		}
		c.formCache = req.PostForm
	}
}

// GetPostFormArray returns a slice of strings for a given form key, plus
// a boolean value whether at least one value exists for the given key.
// 获取 POST、PATCH 或者 PUT 请求的数据,返回切片和bool
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
	c.initFormCache()
	values, ok = c.formCache[key]
	return
}

// PostFormMap returns a map for a given form key.
// 获取 POST、PATCH 或者 PUT 请求的数据,返回键值对
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
	dicts, _ = c.GetPostFormMap(key)
	return
}

// GetPostFormMap returns a map for a given form key, plus a boolean value
// whether at least one value exists for the given key.
// 获取 POST、PATCH 或者 PUT 请求的数据,返回键值对和bool
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
	c.initFormCache()
	return c.get(c.formCache, key)
}

// get is an internal method and returns a map which satisfies conditions.
// 内部方法,能从 Context.queryCache 和 Context.formCache 里获取数据
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
	dicts := make(map[string]string)
	exist := false
	for k, v := range m {
		if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
			if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
				exist = true
				dicts[k[i+1:][:j]] = v[0]
			}
		}
	}
	return dicts, exist
}

// FormFile returns the first file for the provided form key.
// 单文件上传
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	if c.Request.MultipartForm == nil {
		if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
			return nil, err
		}
	}
	f, fh, err := c.Request.FormFile(name)
	if err != nil {
		return nil, err
	}
	f.Close()
	return fh, err
}

// MultipartForm is the parsed multipart form, including file uploads.
// 多文件上传
func (c *Context) MultipartForm() (*multipart.Form, error) {
	err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
	return c.Request.MultipartForm, err
}

// SaveUploadedFile uploads the form file to specific dst.
// 将文件移动保存到目标地址
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
		return err
	}

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}

绑定,在Gin中 分别有JSON、XML、Form、FormPost、FormMultipart、Uri、ProtoBuf、MsgPack、YAML、Header几种类型实现了Binding接口,可用于在请求中构造结构体(tag标签必须与上面的类型所匹配)来绑定请求数据,Gin具体提供两类绑定方法 MustBind 和 ShouldBind 前者如果绑定发生了错误,则请求终止,并响应400状态码,后者如果发生了绑定错误,Gin会返回错误并由开发者处理错误和请求,参数obj一般是一个结构体。一般我Context.ShouldBindJSON()用得比较多,先 var 一个带 tag 的结构体,作为参数调用Context.ShouldBindJSON(),就能获取 JSON 的数据。

func (c *Context) Bind(obj any) error {
	// 可以看看/gin/binding包,差不多就是给结构体的属性赋值
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj any) error {
	return c.MustBindWith(obj, binding.JSON)
}

// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj any) error {
	return c.MustBindWith(obj, binding.XML)
}

// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj any) error {
	return c.MustBindWith(obj, binding.Query)
}

// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) BindYAML(obj any) error {
	return c.MustBindWith(obj, binding.YAML)
}

// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
func (c *Context) BindTOML(obj interface{}) error {
	return c.MustBindWith(obj, binding.TOML)
}

// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj any) error {
	return c.MustBindWith(obj, binding.Header)
}

// BindUri binds the passed struct pointer using binding.Uri.
// It will abort the request with HTTP 400 if any error occurs.
func (c *Context) BindUri(obj any) error {
	if err := c.ShouldBindUri(obj); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
		return err
	}
	return nil
}

// MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs.
// See the binding package.
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
	if err := c.ShouldBindWith(obj, b); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
		return err
	}
	return nil
}

// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
//
//	"application/json" --> JSON binding
//	"application/xml"  --> XML binding
//
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (c *Context) ShouldBind(obj any) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.ShouldBindWith(obj, b)
}

// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj any) error {
	return c.ShouldBindWith(obj, binding.JSON)
}

// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj any) error {
	return c.ShouldBindWith(obj, binding.XML)
}

// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj any) error {
	return c.ShouldBindWith(obj, binding.Query)
}

// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) ShouldBindYAML(obj any) error {
	return c.ShouldBindWith(obj, binding.YAML)
}

// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
func (c *Context) ShouldBindTOML(obj interface{}) error {
	return c.ShouldBindWith(obj, binding.TOML)
}

// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj any) error {
	return c.ShouldBindWith(obj, binding.Header)
}

// ShouldBindUri binds the passed struct pointer using the specified binding engine.
func (c *Context) ShouldBindUri(obj any) error {
	m := make(map[string][]string)
	for _, v := range c.Params {
		m[v.Key] = []string{v.Value}
	}
	return binding.Uri.BindUri(m, obj)
}

// ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
	return b.Bind(c.Request, obj)
}

// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
// body into the context, and reuse when it is called again.
//
// NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
	var body []byte
	if cb, ok := c.Get(BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = cbb
		}
	}
	if body == nil {
		body, err = io.ReadAll(c.Request.Body)
		if err != nil {
			return err
		}
		c.Set(BodyBytesKey, body)
	}
	return bb.BindBody(body, obj)
}

Cookie 的存取。

// 设置cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		SameSite: c.sameSite,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

// 获取cookie
func (c *Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	val, _ := url.QueryUnescape(cookie.Value)
	return val, nil
}

Gin支持这几种渲染,JSON、IndentedJson、SecureJSON、XML、String、Data、Redirect、HTML、HTMLDebug、HTMLProduction、YAML、Reader、ProtoBuf、AsciiJSON,它们都实现了Render接口中的Render,可看/gin/render包。

func (c *Context) Render(code int, r render.Render) {
	c.Status(code)

	if !bodyAllowedForStatus(code) {
		r.WriteContentType(c.Writer)
		c.Writer.WriteHeaderNow()
		return
	}

	if err := r.Render(c.Writer); err != nil {
		panic(err)
	}
}

// HTML renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, obj any) {
	instance := c.engine.HTMLRender.Instance(name, obj)
	c.Render(code, instance)
}

// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
// It also sets the Content-Type as "application/json".
// WARNING: we recommend using this only for development purposes since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead.
func (c *Context) IndentedJSON(code int, obj any) {
	c.Render(code, render.IndentedJSON{Data: obj})
}

// SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json".
func (c *Context) SecureJSON(code int, obj any) {
	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
}

// JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj any) {
	callback := c.DefaultQuery("callback", "")
	if callback == "" {
		c.Render(code, render.JSON{Data: obj})
		return
	}
	c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}

// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj any) {
	c.Render(code, render.JSON{Data: obj})
}

// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
// It also sets the Content-Type as "application/json".
func (c *Context) AsciiJSON(code int, obj any) {
	c.Render(code, render.AsciiJSON{Data: obj})
}

// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj any) {
	c.Render(code, render.PureJSON{Data: obj})
}

// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj any) {
	c.Render(code, render.XML{Data: obj})
}

// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj any) {
	c.Render(code, render.YAML{Data: obj})
}

// TOML serializes the given struct as TOML into the response body.
func (c *Context) TOML(code int, obj interface{}) {
	c.Render(code, render.TOML{Data: obj})
}

// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj any) {
	c.Render(code, render.ProtoBuf{Data: obj})
}

// String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...any) {
	c.Render(code, render.String{Format: format, Data: values})
}

// Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) {
	c.Render(-1, render.Redirect{
		Code:     code,
		Location: location,
		Request:  c.Request,
	})
}

// Data writes some data into the body stream and updates the HTTP code.
func (c *Context) Data(code int, contentType string, data []byte) {
	c.Render(code, render.Data{
		ContentType: contentType,
		Data:        data,
	})
}

// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {
	c.Render(code, render.Reader{
		Headers:       extraHeaders,
		ContentType:   contentType,
		ContentLength: contentLength,
		Reader:        reader,
	})
}

可以在 gin 项目主目录下的 binding 和 render 继续阅读相关源码。