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是框架的实例,它包含了复用器、中间件和各类配置。
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
}
未完待续…