Gin框架内部概览

gin是一款小巧高性能的web服务器框架,本文就gin的一些工作流程做一些介绍!

我们在使用Gin框架的时候,大致流程为:
在这里插入图片描述
这里我们先预设几个问题:

  1. Gin是怎么接管Golang的http服务的?
  2. 初始化Gin的时候初始化了什么内容?
  3. 我们创建的中间件最Gin内部是怎么存储怎么使用的?
  4. 创建的接口是怎么存储跟怎么使用的?
  5. 如何调用?

问题1:Gin是怎么接管Golang的http服务的?
记得我们是如何使用Gin的吗?如下:

server := &http.Server{
		Addr:           addr,
		Handler:        router.Get(),
		ReadTimeout:    config.App.ReadTimeout,
		WriteTimeout:   config.App.WriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}
	_ = server.ListenAndServe()

其中,我们会将自己定义的api接口对应golang内部http.Server结构中的Handler,当我们追踪源码到Server结构中的时候可以发现Handler其实是个接口:

Server结构体:
type Server struct{
 ...
 Handler Handler
 ...
}
Handler接口:
type Handler interface{
	ServeHTTP(ResponseWriter,*Request)
}

在Go中我们清楚,只要实现了某个接口,那么接收该接口的地方同样可以接收实现了这个接口的实例。知道这一点我们可以去Gin的源码中查找对应的方法就可以知道了。
经过查找我们找到,确实在gin.go文件中,Gin的实现Engine存在这个方法的实现:

// ServeHTTP conforms to the http.Handler interface.
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)
}

所以现在我们知道了,gin是通过该方法让golang原生的http将接口请求放置到该方法中的。

问题2:初始化Gin的时候初始化了什么内容?
我们可以简单的贴一下gin的实例,New() *Engin函数中的内容,再根据里面的参数针对几个特别的做个解释:

func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJsonPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

参数注解:
RouterGroup:关于中间件,接口的创建存储都是从这里开始的。
RemoveExtraSlash:设置为true可以解决api路径出现错误数量的/ . 的情况。
trees:用来存储api接口
pool:对象复用池,防止被gc。
需要知道的是,在编译执行后(初始化的时候),程序将会把我们创建的中间件,接口全部存储到内存中(切片)。

问题3:我们创建的中间件最Gin内部是怎么存储怎么使用的?
在gin中,我们使用use使用一个中间件,所以可以根据该方法来查找,中间件被创建之后,是存储在哪里的,通过查找,发现是在RouterGroup中的Handlers里面,并且是一个自定义的类型:HandlersChain,该自定义的原始类型为:HandlersChain []HandlerFunc,HandlerFunc任然是一个自定义类型,它的原始类型为:type HandlerFunc func(*Context),这个func里面的Context是gin自定义的Context。

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

可以看到所有的中间件都是存储在group.Handlers中的。
关于中间件是怎么使用的,这里暂时不讲,等到跟api接口一起来探讨。

问题4:创建的接口是怎么存储跟怎么使用的?
接口的创建有两种:

  1. 使用Group创建api分组
  2. 直接创建api接口
    其实,无论是使用group创建还是直接创建,内部都是通过RouterGroup结构来创建。我们照样通过源码来证明:
    我们首先在代码中写一个简单的案例来查看:
//通过group创建
r.Group("/aa")
//直接创建
r.GET("/a", func(c *gin.Context) {
		c.String(http.StatusOK, "successful  ")
	})

然后我们点击Group跟GET方法进去:

//routergroup.go
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}
//routergroup.go
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

可以很清楚的看到,GET是属于RouterGroup的方法,所以实际上,当我们使用GET直接创建后,实际上使用的是初始化的时候的那个RouterGroup对象,然后当我们主动使用Group的时候,是表示额外再创建一个RouterGroup对象。
我们的API处理函数的类型都为:HandlerFunc,继续拿Group(relativePath string, handlers …HandlerFunc)方法来看,该方法的第二个可变参数表示的是属于该组的中间件。

提问:中间件的作用是为了在请求到达接口之前做些额外的处理,当我们有直接通过实例中的默认RouterGroup创建的中间件跟自己创建的Group里面创建的中间件,这两个地方的中间件是如何在一个api请求前同时生效的呢?
解答:
RouterGroup中存有一个HandlersChain类型的Handlers这是一个切片。在我们使用Group创建一个api组的时候,会通过group.combineHandlers(handlers)将外部初始化时的中间件复制到当前自定义的RouterGroup里面的Handlers中:

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

当复制到group里面的时候,你要知道它并不是中间件的终点,它的终点是在api接口上面。
api在创建的时候会调用handle方法将前面的中间件集合放在当前api处理函数之前,这样做的目的是为了保证中间件的执行时间在处理函数之前!

//routergroup.go
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)
}

我们可以看到,实际上最终api接口的归宿在于gin实例engine中的tress对象。该对象是一个methodTree的切片类型:

type methodTrees []methodTree
type methodTree struct {
	method string
	root   *node
}
type node struct {
	path      string
	indices   string
	children  []*node
	handlers  HandlersChain
	priority  uint32
	nType     nodeType
	maxParams uint8
	wildChild bool
	fullPath  string
}
func (n *node) addRoute(path string, handlers HandlersChain) {
	.....
	// Empty tree
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(numParams, path, fullPath, handlers)
		n.nType = root
		return
	}
	.....
}

所有的接口最后都按照http请求方法不同,全部放在tress中。
并且每个接口都含有全部的公共中间件。

问题5:如何调用?

内部调用任然是在实现ServeHTTP方法的地方

//gin.go
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)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
	....

	if engine.RemoveExtraSlash {
		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
		value := root.getValue(rPath, c.Params, unescape)
		if value.handlers != nil {
			c.handlers = value.handlers
			c.Params = value.params
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		....
	}

	....
}

当value := root.getValue(rPath, c.Params, unescape)获取到存储到的接口之后,会通过c.Next()方法去调用,Next是不是很熟悉?没错,这个方法经常被用在中间件里面。

//context.go
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

可以看到方法中通过for循环来依次遍历当前接口的handlers,handlers集合前面部分存放的是中间件(如果有的话),后面才是api处理函数。
c.index默认值是-1,所以第一次进来需要进行自增操作。因为Next方法是整个gin框架调用中间件跟api处理函数的地方,并且单个api接口跟中间件全部在同一个切片集合,所以当在某个中间件里面调用next之后,那么该中间件里面Next方法之后的操作将在该接口所拥有的所有中间件方法跟api处理函数之后执行!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值