【源码解读】Gin 框架 (一)

写在前面

我们今天就从下面这几行简单的代码中,探讨gin框架的底层实现
gin的底层是基于net/http包实现的,所以很多gin底层源码中涉及到了很多net/http的相关方法。

本文全部基于[email protected]进行讲解

package main

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

func main() {
    
    
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
    
    
		c.JSON(200,"pong")
	})
	_ = r.Run(":3000")
}

1. Run 函数底层实现

  • gin/gin.go 文件
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.")
	}
(1)	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
(2)	err = http.ListenAndServe(address, engine.Handler())
	return
}

这段代码还是比较容易看懂的。

  • address := resolveAddress(addr) 将传入的addr进行判断,返回正确的端口。
  • 调用http.ListenAndServe 对这个端口进行监听,并将框架的信息引擎传进入。

然后让我们来看看这个ListenAndServe的具体实现

  • net/http/server.go 文件

这个链接是基于TCP网络进行监听连接的,并且request和response都通过这个handler进行传递。

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

然后我们来看一下这个Handler对象是如何实现处理请求和响应

type Handler interface {
    
    
	ServeHTTP(ResponseWriter, *Request)
}

这个Handler实现一个ServerHTTP的接口,来处理Response和Request,既然gin的Engine能和net/http包的Handler进行一个无缝连接,那么我们可以看看在这个gin包中,这个Engine是如何实现Handler()方法的。

接着我们来看一下这个gin的引擎对象 *Engine 实现的ServerHTTP方法

  • gin/gin.go 563行
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    
    
	c := engine.pool.Get().(*Context)
	// 创建上下文对象,注意这里是gin封装的Context,并不是go原生的Context!
	// 这里用到了sync.Pool来进行内存的复用,防止频繁创建上下文,而导致性能的下降
	c.writermem.reset(w)
	c.Request = req 
	// 对请求进行赋值,并将这个req请求放到context的Request上下文中。
	c.reset()

	engine.handleHTTPRequest(c) // 处理请求

	engine.pool.Put(c) 	// 对上下文对象进行回收
}

那这个Engine的handleHTTPRequest() 方法究竟是怎么处理请求的呢?

扫描二维码关注公众号,回复: 14271040 查看本文章
  • gin/gin.go 585行
func (engine *Engine) handleHTTPRequest(c *Context) {
    
    
	httpMethod := c.Request.Method // 获取请求的方法
	rPath := c.Request.URL.Path // 获取URL请求地址
	{
    
    ...对请求地址进行判断处理}

	t := engine.trees // 获取压缩前缀树数组,每个请求方法都有一颗radix树。
	for i, tl := 0, len(t); i < tl; i++ {
    
    
		if t[i].method != httpMethod {
    
     
		// 找到当前请求方式对应的radix树
			continue
		}
		root := t[i].root // 得到树的根节点
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		// 根据请求路径获取匹配的redix树节点
		if value.params != nil {
    
    
			c.Params = *value.params
		}
		if value.handlers != nil {
    
    
		// 如果这个路由处理器数组不为空,逐个调用处理器处理请求,响应给客户端
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			// 调用第一个处理器处理请求
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		// {...请求后续处理,比如没有该方法之类的处理}
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

那你可能会对这个c.Next()感到疑惑,这个是如何对请求进行处理的呢?

  • gin/context.go 170 行
func (c *Context) Next() {
    
    
	c.index++ 
	// 指向要执行的中间件,初始值为-1,对这个index进行自增操作
	for c.index < int8(len(c.handlers)) {
    
    
	// 遍历所有的处理器,一次调用他们来处理请求
		c.handlers[c.index](c)
		// 使用中间件处理请求,中间件可以改变c.index的值
		c.index++
		// 然后再进行自增
	}
}

c.handlers 中是可以处理所有的路径请求,因为已经遍历完所有的c.index了,所以调用这个Next()就可以处理所有的命令。

然后我们看看这个Next()方法下面的另外两个方法IsAborted()Abort()

  • IsAborted() 判断是否已经终止处理器调用
func (c *Context) IsAborted() bool {
    
    
	return c.index >= abortIndex
}
  • Abort() 终止处理器调用
func (c *Context) Abort() {
    
    
	c.index = abortIndex
}

2. Engine 引擎对象初始化

我们一般会有两种形式的对象初始化,一个是 gin.New() 另一个是 gin.Default()

  • gin/gin.go 209行
    Default()其实就是New()之后新加了两个中间件而已
func Default() *Engine {
    
    
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

我们来重点看一下New()的实现。

func New() *Engine {
    
    
	debugPrintWARNINGNew()
	engine := &Engine{
    
     // 初始化Engine对象
		RouterGroup: RouterGroup{
    
     // 初始化路由组对象
			Handlers: nil,
			basePath: "/",
			root:     true, // 设置该路由器组为根节点
		},
		
		FuncMap:                template.FuncMap{
    
    },
		RedirectTrailingSlash:  true,
		// 为true,如果只有/hello的路由存在,会将请求/hello/ 请求重定向到/hello , GET 响应到301, 其他响应到307。
		RedirectFixedPath:      false,
		// 如果找不到路由,尝试修复请求路径。例如 /HELLO 和 /../../HEllo 可以重定向到/hello。
		HandleMethodNotAllowed: false,
		// 是否对不允许的方法,做对应的响应;开启后,入股用POST方法请求[GET /user] ,请求将由[GET /user]处理
		
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{
    
    "X-Forwarded-For", "X-Real-IP"},
		TrustedPlatform:        defaultPlatform,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		// 提供给http.Request的ParseMultipartForm方法调用的“maxMerory”参数的值。默认是32MB
		trees:                  make(methodTrees, 0, 9),
		// 创建容量为9的redix树切片,对应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 {
    
    
	// 设置 sync.Pool 新建上下文对象函数
		return engine.allocateContext()
	}
	return engine
}

3. Router路由

3.1 Group路由器组

r.Group() 可以帮助我们更快归纳某种请求。

  • gin/routergroup.go 文件

创建路由组,仅是返回路由组对象,路由组的本质就是一个模板,使用路由组添加路由,省去用户填写相同路径前缀和中间件的步骤

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    
    
	// 返回一个路由组对象
	return &RouterGroup{
    
    
	// 新路由器组继承父路由器组的所有处理器
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		// 将绝对路径计算成相对路径
		engine:   group.engine,
	}
}
  • combineHandlers()方法
type HandlersChain []HandlerFunc

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    
    
	finalSize := len(group.Handlers) + len(handlers)
	// 将原来的处理器长度加上放入当前需要追加的处理器长度
	assert1(finalSize < int(abortIndex), "too many handlers")
	// 如果超过了63中间件,这个路由是无法进行一个添加的,太多中间件要处理了。
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	// 把旧的中间件都拷贝到新创建的切片中
	copy(mergedHandlers[len(group.Handlers):], handlers)
	// 把新的也追加到这个新的创建的切片中
	return mergedHandlers
}
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    
    
	return joinPaths(group.basePath, relativePath)
	// 根据绝对路径进行拼接成相对路径
}

3.2 GET 路由

路由是怎么进行注册的呢?我们通过GET方法来了解一下是怎么处理的

  • gin/routergroup.go
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    
    
	return group.handle(http.MethodGet, relativePath, handlers)
}

然后我们来看看这个handle()方法

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    
    
	absolutePath := group.calculateAbsolutePath(relativePath)
	// 根据相对路径,计算绝对路径
	handlers = group.combineHandlers(handlers)
	// 合并处理器(实际上就是将handlers追加到原有的处理器组切片中,作为该路径的处理链)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	// 添加路由,涉及radix树添加节点方法。
	return group.returnObj()
}

4. Context 上下文

注意一点:这个上下文是Gin构建的,与Go原生的Context是不一样的

type Context struct {
    
    
	writermem responseWriter
	Request   *http.Request	 // 请求对象
	Writer    ResponseWriter // 响应对象
	Params   Params 		 // 路由参数 /user/:id 这个id
	handlers HandlersChain	 // 中间件数组 
	index    int8 		// 当前执行中间件的下标
	fullPath string  	// 请求的完整路径

	engine       *Engine
	params       *Params
	skippedNodes *[]skippedNode 

	mu sync.RWMutex 	// 保证Keys map的线程安全
	Keys map[string]any // 对每一个请求进行处理存储

	Errors errorMsgs 	// 存储错误的列表
	Accepted []string
	queryCache url.Values // 存放url请求参数
	formCache url.Values  // 存放form参数
	sameSite http.SameSite
}
  • 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 传递过程进行拷贝

如果上下文在携程之间进行传递,那么必须要使用拷贝,传递副本。

因为context在处理完一个请求之后,就变成nil了,所以为了其他使用这个context的不报错,所以采用的是拷贝,防止被回收。拷贝是不会被回收的

func (c *Context) Copy() *Context {
    
    
	cp := Context{
    
    
		writermem: c.writermem,
		Request:   c.Request,
		Params:    c.Params,
		engine:    c.engine,
	}
	cp.writermem.ResponseWriter = nil
	cp.Writer = &cp.writermem
	cp.index = abortIndex
	cp.handlers = nil
	cp.Keys = map[string]any{
    
    }
	for k, v := range c.Keys {
    
    
		cp.Keys[k] = v
	}
	paramCopy := make([]Param, len(cp.Params))
	copy(paramCopy, cp.Params)
	cp.Params = paramCopy
	return &cp
}

5. 思考

  • 如果上下文对象的创建,可以用sync.Pool 来复用内存。
  • 如果上下文需要被并发使用,需要使用上下文副本。

猜你喜欢

转载自blog.csdn.net/weixin_45304503/article/details/125090876