go-gin源码分析
gin的http服务启动
test := gin.Default()
test.Run()
上面简简单单的两行代码,就能开启一个基于gin框架的http服务
下面复杂的这么多行代码也是在启动一个基于gin的http服务。
了解过GOhttp包的应该很好理解下面的代码
//返回一个gin的engine对象
func InitRouter() *gin.Engine{...}
//构建一个server对象,传入engine作为handler
s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.ServerSetting.HttpPort),
Handler: router,
ReadTimeout: setting.ServerSetting.ReadTimeout,
WriteTimeout: setting.ServerSetting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}
//将http作为协程开启
go func() {
if err := s.ListenAndServe(); err != nil {
log.Printf("Listen: %s\n", err)
}
}()
//监听系统的信号(crtl+c)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<- quit
log.Println("Shutdown Server ...")
//利用contex来关闭http服务(防止服务意外关闭所以留5s的扫尾时间)
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
Gin默认的启动方式
我们先来看看两行代码的http服务是怎么开启的
func Default() *Engine {
//检验版本
debugPrintWARNINGDefault()
//生成一个engine对象
engine := New()
//默认使用Logger,Recovery中间件
engine.Use(Logger(), Recovery())
return engine
}
//参数就是返回一个Engine对象(如果一个函数太长,先看参数和返回值了解大概是干什么的)
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
//路由组
// 给框架实例绑定上一个路由组
RouterGroup: RouterGroup{
// engine.Use 注册的中间方法到这里
Handlers: nil,
basePath: "/",
// 是否是路由根节点
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: 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
}
还有Run方法
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
// 执行http包的ListenAndServe方法 启动路由
// engine实现了http.Handler接口 所以在这里作为参数传参进去
// 后面我们再看engine.ServeHTTP的具体逻
err = http.ListenAndServe(address, engine)
return
}
了解了这个其实就很容易理解第二个是启动gin http服务的。
那么engine
为什么能作为handler
传入server呢,那肯定是实现了ServeHTTP
方法即实现了handler接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 获取一个上下文实例
// 从实例池获取 性能高
c := engine.pool.Get().(*Context)
// 重置获取到的上下文实例的http.ResponseWriter
c.writermem.reset(w)
// 重置获取到的上下文实例*http.Request
c.Request = req
// 重置获取到的上下文实例的其他属性
c.reset()
//这里重置是应为从对象池中拿出来的contex可能具有脏数据
//(也就是可能是其他请求的contex,有兴趣可以试着注释掉这里的重置)
//看看会发生什么
// 实际处理请求的地方
// 传递当前的上下文
engine.handleHTTPRequest(c)
//归还上下文实例
engine.pool.Put(c)
}
果然 engine实现了ServeHTTP
方法那么在处理请求时,也是主要调用了这个方法。
接下来就好好分析engine以及engine的这个ServeHTTP方法做了什么事
engine
type Engine struct {
//这里的组合代表engine也实现了RouterGroup(就当作继承吧)
RouterGroup //路由组
....//去掉一些不太重要的结构
pool sync.Pool//contex的对象池
trees methodTrees//handler树
}
type methodTrees []methodTree
type methodTree struct {
method string
root *node
}
type node struct {
path string
indices string
children []*node //子节点
handlers HandlersChain //handler链
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
type HandlerFunc func(*Context) //Contex也算是gin的核心了,稍后探索
type HandlersChain []HandlerFunc
这些结构稍后再探索,先从一个gin的流程开始
gin注册路由
路由其实分为两个部分,一个是路由设置部分,一个是路由匹配部分。
路由其实并不仅仅是url,还包括HTTP的请求方法,而实现一个REST风格的http请求,需要支持REST支持的方法,比如GET,PUT,POST,DELETE,OPTION等。
r.GET("/auth", api.GetAuth)
r.Handle(http方法,path,handler)
而gin中注册路由的方式就是调用对应的http方法,或是使用r.Handle注册这里的r是engine
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
//向路由组注册路由
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
//合并中间件得handle和路由的handle
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
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,是RouterGroup的handler在用户自定义handler前面。
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
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)
// 检查有没有对应method集合的路由
//所以这里是每一个方法一个树
root := engine.trees.get(method)
if root == nil {
// 没有 创建一个新的路由节点
root = new(node)
root.fullPath = "/"
// 添加该method的路由tree到当前的路由到路由树里
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
可能这里有点晕,但是只要记住handlers
或者handlerschain
都是func(*Contex)
这个方法,而这个Contex
里面有着GO原生http服务处理所有的的东西就好了,把它看成加强版的处理业务逻辑的对象
所以上述的这些函数就把路由与对应的handlers设置好了。
那么来看看路由是如何匹配的
路由匹配
还记得前面engine
实现的ServeHTTP
方法吗
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 获取一个上下文实例
// 从实例池获取 性能高
c := engine.pool.Get().(*Context)
// 重置获取到的上下文实例的http.ResponseWriter
c.writermem.reset(w)
// 重置获取到的上下文实例*http.Request
c.Request = req
// 重置获取到的上下文实例的其他属性
c.reset()
//这里重置是应为从对象池中拿出来的contex可能具有脏数据
//(也就是可能是其他请求的contex,有兴趣可以试着注释掉这里的重置)
//看看会发生什么
// 实际处理请求的地方
// 传递当前的上下文
engine.handleHTTPRequest(c)
//归还上下文实例
engine.pool.Put(c)
}
真正处理的就是handleHTTPRequest
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++ {
// 这里寻找当前请求method的路由树节点
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 {
// 把找到的handles赋值给上下文
c.handlers = value.handlers
// 把找到的入参赋值给上下文
c.Params = value.params
c.fullPath = value.fullPath
// 执行handle
c.Next()
// 处理响应内容
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.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 value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
这里就通过getValue找到了对应的路由进行处理
gin的中间件
我们看到tree中路由对应的是HandlersChain,实际就是[]HandlerFunc,所以一个路由,实际上会对应多个handlers。
所以是可以一个路由拥有多个handler的。这里的handler是怎么来的呢?
每个路由的handler有几个来源,第一个来源是在engine.GET的时候调用增加的。第二个来源是RouterGroup.GET的时候增加的(而设置的方式上面已经说过了),而且group的handler高于自定义的handler(告诉我们中间件的回调要先于用户定义的路径处理函数。),这里自定义的handler可以是多个。
router.GET("/before", MiddleWare(), func(c *gin.Context) {
request := c.MustGet("request").(string)
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
})
})
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("before middleware")
c.Set("request", "clinet_request")
c.Next()
fmt.Println("before middleware")
}
}
第三种方法是使用Use增加中间件的方式:
apiv1.Use(jwt.JWT())
,apiv1 := r.Group("/api/v1")
这里的apiv1是一个路由组,gin一般使用一个use来载入一个中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
这里会把这个中间件(实际上也是一个handler)存放到routerRroup上。所以中间件是属于groupHandlers的。
上文说过handleHTTPRequest
将对应路由的handler赋给了当前请求的上下文,并且调用next()
函数执行。
func (c *Context) Next() {
// 上下文处理之后c.index被执为-1
c.index++
for c.index < int8(len(c.handlers)) {
// 遍历执行所有handle(其实就是中间件+路由handle)
c.handlers[c.index](c)
c.index++
}
}
之前也提到过,context是从对象池里取出来的,所以可能带有脏数据,因此要重置
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
}
这里的Next方法没有使用局部变量去遍历计数handlers的,它使用了和Context的成员变量index。这样就可以保证某些情况下Next()函数不会触发任何handler的调用。
这里还有个细节,既然这里是调用c.Next()
来交出当前Contex的控制权,并执行接下来的hanlder所以c.Next就可以视作是后面所有的handler都执行了。那么整个handler执行和返回的过程就可以看作一个栈,先执行的最后返回。
好了说到这里,,整个gin的流程应该已经明了了t
接下来探索一下前文提过多次的Context
Context
type Context struct {
writermem responseWriter
Request *http.Request
// 传递接口,使用各个处理函数,更加灵活,降低耦合
Writer ResponseWriter
Params Params // 路径当中的参数
handlers HandlersChain // 处理函数数组
index int8 // 目前在运行着第几个处理函数
fullPath string
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
//意味着这里可以在中间件之间传递参数
Keys map[string]interface{}
......
}
可以看到Context里集成Request
和ResponseWriter
,就是说整个业务逻辑处理的参数数据都封装在这个Context里了
里面实现了几乎所有的http里处理的功能函数(bind(获取参数),render输出参数)
附上一份gin的源码(自己学习的时候写的,有比较详细的注释)