gin源码--处理到来的Request

288 阅读3分钟

思路

带着问题去分析,从如下思路来分析gin的处理流程

  1. gin的中间件以及最终各自的业务处理逻辑是如何添加到路由中的?
  2. gin启动后,当一个请求request到来时,执行流程是怎样的?

定义路由

gin的中间件使用如下方法添加

// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

gin的group除了以上方法,还可以在初始化时传入

        // 1、使用初始化时传入
        group1:=g.Group("/g1", func(c *gin.Context) {
			log.Println("before group1 middleware1")
			c.Next()
			log.Println("after group1 middleware1")
		})
       // 2、使用use添加
		group1.Use(func(context *gin.Context) {
			log.Println("before group1 middleware2")
			c.Next()
			log.Println("after group1 middleware3")
		})
		 

如下添加我们的业务处理逻辑,可以看到这里使用的也是 func(c *gin.Context) 签名,与中间件使用的签名一致。

     g.Handle("GET", "/h1", func(c *gin.Context) {
			c.JSON(http.StatusOK, Response{
			Code: 10000,
			Msg:  "this is h1",
			Data: struct {
			}{},
		})
	})


通过层层查看group.Handle的源码,最终我们看到一个 combineHandler 方法


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

combineHandlers方法 ,将我们之前添加到 group.handlers 的中间件 handlerFunc 与我们最终在 Handle 里添加的包含业务逻辑的 handleFunc 组合在一起。
换个说法,就是通过 group.handle 添加的业务逻辑处理函数,都会与 group.handlers 最终组合成处理链,然后与路径relativePath一起,加入到路由中。每一个请求,最终执行的是处理链。

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

处理请求

了解了处理逻辑最终是怎样添加到gin的路由中的,那当前端发起api访问,gin收到一个request请求时,如何执行中间件和最终的业务处理逻辑的呢?

gin的启动

    err:=g.Run(":8080")
	if err != nil {
		log.Fatal(err)
	}    


查看gin.Run的源码,其实就是对 http.ListenAndServe 的封装,熟悉net/http包的都知道,只要实现了 http.Handler 接口,即可处理网络请求。

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

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

进一步查看gin的 serverHttp方法实现,可以看到最终是使用 engine.handleHTTPRequest(c) 对请求request进行处理。另外,在serverHttp 的方法里,将http.ResponseWriter,*http.Request封装到了gin自定义的Context中,供后续的流程使用。这就是 func(c *gin.Context) 签名中 Context 的来源。

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    
    // 这里获得的context对象会进行重置,清除上一请求的属性
    
    // 从sync.pool中获得context对象
	c := engine.pool.Get().(*Context)
    
    // 重设http.ResponseWriter
	c.writermem.reset(w)
    
    // 重设*http.Request
	c.Request = req
    // context对象重置
	c.reset()

    // 进行本次请求的处理
	engine.handleHTTPRequest(c)
    
    // 将context对象放进sync.Pool复用
	engine.pool.Put(c)
}

重点从22行代码开始,通过解析出Request的方法和路径,从路由中获得先前添加到路由中的处理链handlers,
然后赋值给 Context,接着执行c.Next(),开始递归执行中间件和自定义的业务处理逻辑。

func (engine *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}
	rPath = cleanPath(rPath)

	// Find root of the tree for the given HTTP method
	t := engine.trees
    
    //循环从前缀树树种查找添加到路由中的处理链
    
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		handlers, params, tsr := root.getValue(rPath, c.Params, unescape)
		if handlers != nil {
            // 处理链赋值给context实例
			c.handlers = handlers
			c.Params = params
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		if httpMethod != "CONNECT" && rPath != "/" {
			if 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
			}
			if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

c.Next(),其实就是按照顺序执行handlerChain即注册到路由中的处理链。

总结

  1. gin的中间件和自定义的处理函数,都是func(c *gin.Context) 签名。
  2. 路由中最终保存的是带有中间件的处理链handlers。
  3. gin的Context包含了handlers处理链,http的ResponseWriter和Request,封装了获取参数以及返回接口的方法,贯穿整个处理流程。