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