go web框架 gin-gonic源码解读01————Engine

go web框架 gin-gonic源码解读01————Engine


gin-gonic是go语言开发的轻量级web框架,性能优异,代码简洁,功能强大。有很多值得学习的地方,最近准备把这段时间学习gin的知识点,通过engine,context,router,middleware几篇博客文章总结总结。

而Engine是gin框架最核心的结构体。
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
// ... 略
}

为什么gin需要设计一个Engine结构体?
因为gin框架依赖于go本身的 net/http 包来提供http服务。 net/http 包的http服务可以用以下方式快速的启动:

type mHandle struct {
}

func (i mHandle ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("Hello"))
}

func HttpRun() {
	// mHandle{} 实现了 net/http中的Handler接口
	http.Handle("/", mHandle{})
	http.ListenAndServe(":9999", nil)
}
// net/http中的Handler接口
// type Handler interface {
//	 ServeHTTP(ResponseWriter, *Request)
// }

而我们gin框架的Engine也实现了一个net/http包的Handler接口。当是gin既然是依赖老的net/http为什么大家不直接使用net/http,而是需要使用gin呢,那是因为net/http在大多数情况下只支持静态路由,而且不能很好的支持动态路由,对中间件的开发也不友好,也不能很好的支持http模版的返回,所以大多数时候我们更倾向于使用集成了这些功能的gin,并且gin的代码量很少,简直是小而美。

type Engine struct {
	// ... 略

	// 对象池,这里用于存放gin.Context对象,减少内存分配,降低 GC 压力。
	pool	sync.Pool
	// 路由树
	trees  methodTrees
}

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Context是gin框架为了更方便的处理http的请求与响应,(即w http.ResponseWriter, req *http.Request)
	// 而对进行的封装,每次接受到http请求都需要封装一下Context结构体,交由下一步代码执行,Context在后续的博客中会有详细的介绍
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	// 将请求交于逻辑函数执行
	engine.handleHTTPRequest(c)

	// 执行完了进行归还
	engine.pool.Put(c)
}

// 逻辑函数,这里来解析请求的url,然后路由匹配该路径需要执行的方法
func (engine *Engine) handleHTTPRequest(c *Context) {
	// 获取请求方法GET,POST..
	httpMethod := c.Request.Method
	// 获取url
	rPath := c.Request.URL.Path
	unescape := false
	// 如果地址存在原始地址,则使用原始地址
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}

	if engine.RemoveExtraSlash {
		// cleaenpPath 函数作用类似于filepath.Clean(),是为了获取最短有效url
		rPath = cleanPath(rPath)
	}

	// Find root of the tree for the given HTTP method
	// engine.trees中存放是gin框架的路由树,它采用前缀树结构来搞笑的存储各类路由	
	// 后续的博客会对路由树有更为详细的介绍,这里就简单介绍一下。
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		// 路由树的第一层孩子节点都是请求方法,如GET,POST。。。
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		// 查找请求的url是否有对应的url路由配置
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil {
			c.Params = *value.params
		}
		// value.handlers存储的就是该路由的逻辑处理方法
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			// c.Next是gin框架调用handlers与各类中间件的一种便捷的方式,后续讲中间件的时候会重点介绍。
			c.Next()
			// 处理完了,写入响应的头文件
			c.writermem.WriteHeaderNow()
			return
		}

		// 执行到这里了说明value.handlers == nil,这里判断是不是重定向请求,然后进行重定向处理
		if httpMethod != http.MethodConnect && rPath != "/" {
			if value.tsr && engine.RedirectTrailingSlash {
				redirectTrailingSlash(c)
				return
			}
	
			// 实在找不到,就尝试修复你的url看看能不能找到合适的路由来处理
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
				return
			}
		}
		break
	}

	// HandleMethodNotAllowed 这个配置如果开启,并且没有找到合适的路由来处理该请求,就会尝试别的method 会不会有可以解析该请求的路由
	if engine.HandleMethodNotAllowed {
		for _, tree := range engine.trees {
			// 相同method的上面已经找过了,这里continue
			if tree.method == httpMethod {
				continue
			}
			// 到别的请求方法下面嚯嚯
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	// 啥招都没有了,调用统一的失败处理函数,响应该请求
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}