看看Gin框架是如何实现的

基本使用

gin是一个高性能的golang web框架,它的代码其实很少,相对于spring来说,搞懂gin真是砍瓜切菜

先来看一下gin的基础用法

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

func Init() {
	r := gin.Default()
	initRoute(r)
	r.Run()
}

func initRoute(r *gin.Engine) {
	r.GET("ping", handler)
}

func handler(ctx *gin.Context) {
	ctx.String(200, "Hello World!")
}

这个例子里,我们启动了一个简单的web服务,使用gin.Default创建一个Engine对象,Engine是Gin核心中的核心,我们后面会具体讲解。之后通过initRoute注册一个路由,当访问localhost:8080/ping的时候,就会返回Hello World!。

最后调用r.run让服务启动。只需要很少的几行代码,就能通过gin启动一个web服务。我们主要探索它的实现原理,关于gin更多的语言特性和用法这里就不细说了,更多例子可以前往Gin Examples.

Engine

官网解释:Engine是框架的实例,它包含了复用器、中间件和各类配置。

type Engine struct {
	// 路由组
	RouterGroup

	// 如果设置为true,如果/foo/没有匹配到路由,自动重定向到/foo
	RedirectTrailingSlash bool

	// 和RedirectTrailingSlash类似,对path做一些修正
	RedirectFixedPath bool

	HandleMethodNotAllowed bool

	ForwardedByClientIP bool

	// DEPRECATED
	AppEngine bool

	// 是否通过url.RawPath来获取参数
	UseRawPath bool

	// If true, the path value will be unescaped.
	// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
	// as url.Path gonna be used, which is already unescaped.
	UnescapePathValues bool

	RemoveExtraSlash bool

	// List of headers used to obtain the client IP when
	// `(*gin.Engine).ForwardedByClientIP` is `true` and
	// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
	// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
	RemoteIPHeaders []string

	// If set to a constant of value gin.Platform*, trusts the headers set by
	// that platform, for example to determine the client IP
	TrustedPlatform string

	// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
	// method call.
	MaxMultipartMemory int64

	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

Engine里面这么多字段,还有很多方法没往这贴呢,不用着急,我们一点一点的抽丝剥茧,揭开gin的真面目

简单的看,一个gin服务对应一个Engine实例。r = gin.Default()实际上就是创建了一个Engine对象,它的源码也不复杂

func Default() *Engine {
	engine := New()
	// 注册中间件
	engine.Use(Logger(), Recovery())
	return engine
}

func New() *Engine {
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		......
	}
	engine.RouterGroup.engine = engine
	// pool存放context对象
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

首先New一个Engine出来,初始化一些基础结构,然后通过Use方法注入两个中间件logger和recovery,它们的类型是HandlerFunc,logger打日志用的,recovery用来在服务panic是recover掉,然后返回500,避免服务直接崩溃。

有意思的是engine里面有一个字段pool,类型是sync.pool,在创建Engine的时候,我们也对pool进行了初始化,关于sync.pool可以看我的另一片文章【todo】,可以看到pool的New方法创建并返回了一个context,而context是每一个请求都有的,携带上下文信息的对象,可以想象context是非常重的,如果每一个请求都创建并销毁一个context对象,GC肯定撑不住了啊,还怎么成为一个高性能的wen服务器?所以Gin通过sync.pool来达成对context的复用,优化GC。

路由

不管web服务框架是如何实现的,它最基础的功能就是路由,简单来说就是解析url,然后分派不同的handler来处理对应请求。

在创建Engine后,我们还的告诉它,如何处理路由信息。注册路由非常简单,Engine支持RESTful的6种方法。

func initRoute(r *gin.Engine) {
	r.GET("ping", pingHandler)
}

实际上Engine是通过继承了RouterGroup(可以在Engine的结构体里找到)来实现路由处理的,所有的路由处理都在RouterGroup中完成,以Get源码为例

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

当我们通过GET注册路由后,会得到一个IRoutes对象,IRoutes本身是一个interface,定义了一系列方法,其中就包含GET,POST…等,不难发现RouterGroup实现了IRoutes。另外注册路由时,对一个path,不只可以穿入一个处理方法,源码里三个点代表我们可以传入多个handler,之后在请求到达时会逐个被调用。而GET直接调用了内部的group.handle,这里面又是如何实现的呢

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

一共有三步

1、计算绝对路径

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
	return joinPaths(group.basePath, relativePath)
}

其实就是字符串join一下,用basePath和当前注册的path组合。哪里来的basePath,以及为什么要join呢?

我们在创建Engine的时候,其实就注册了一个path,它就是"/",其他后续所有注册的path都挂在它下面,如果把它看成一颗树的root节点,在它下面还有更多的子节点,而一个完整的path,就是从根节点到子节点中间经过的所有Path的组合。实际上Gin对于路由的管理就用到了树的结构,这个后面再讲。

2、添加handlers

当前传进去的handlers是path的,但人家basePath也有handlers,basePath是path的前缀,当你发起请求的时候,是不是所有basePath的handlers都应该过一遍呢。添加的过程也并不负责,就是重新开辟一个数组空间,然后把basePath和path的handlers都拷贝到这里,然后返回数组地址。

3、关联绝对路径和handlers

这一步比较重要,逻辑也比较复杂了,先看看最外层的代码,滤去一些无关痛痒的代码

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	......
	
	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)
	
	......
}

首先根据method获取根节点,metod有哪些?GET, POST, PUT, DELETE…

如果根节点不存在,就创建一个,然后加入到entine.trees里,entine.trees是methodTrees,methodTrees的定义是[]methodTree,所以entine.trees就是一个methedTree数组。抽丝剥茧,methedTree又是什么玩意儿?

type methodTree struct {
	method string
	root   *node
}

type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain
	fullPath  string
}

链路稍微有点长了,画个图更清晰些
看看Gin框架是如何实现的_第1张图片

未完待续…

你可能感兴趣的:(技术,后端,golang,web开发)