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 = reqc.reset()// 将请求交于逻辑函数执行engine.handleHTTPRequest(c)// 执行完了进行归还engine.pool.Put(c)
}// 逻辑函数,这里来解析请求的url,然后路由匹配该路径需要执行的方法
func (engine *Engine) handleHTTPRequest(c *Context) {// 获取请求方法GET,POST..httpMethod := c.Request.Method// 获取urlrPath := c.Request.URL.Pathunescape := false// 如果地址存在原始地址,则使用原始地址if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues}if engine.RemoveExtraSlash {// cleaenpPath 函数作用类似于filepath.Clean(),是为了获取最短有效urlrPath = cleanPath(rPath)}// Find root of the tree for the given HTTP method// engine.trees中存放是gin框架的路由树,它采用前缀树结构来搞笑的存储各类路由 // 后续的博客会对路由树有更为详细的介绍,这里就简单介绍一下。t := engine.treesfor 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.handlersc.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的上面已经找过了,这里continueif tree.method == httpMethod {continue}// 到别的请求方法下面嚯嚯if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}}// 啥招都没有了,调用统一的失败处理函数,响应该请求c.handlers = engine.allNoRouteserveError(c, http.StatusNotFound, default404Body)
}