gin 源码阅读(一)-- 启动
source link: https://studygolang.com/articles/28068
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
文章首发于同名公众号,欢迎关注~
因为 gin
的安装教程已经到处都有了,所以这里省略如何安装, 建议直接去 github
官方地址的 README
中浏览安装步骤,顺便了解 gin
框架的功能。 https://github.com/gin-gonic/gin
最简单的代码
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.Run() // 监听并在 0.0.0.0:8080 上启动服务 }
上面就是使用了 gin
框架的最简单的 demo
,接下来通过这个 demo
去一步步阅读分析 gin
是如何启动的。
gin.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) err = http.ListenAndServe(address, engine) return }
除去第一行和第四行打印,是不是有点像 go
原生的 http
库?来看看 http
库的 web
服务简单 demo
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", indexHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
可以看到 gin
框架启动的时候,也是基于 http.ListenAndServe(addr string, handler Handler)
这个方法的,与原生的 http
库的区别就是, ListenAndServe(addr string, handler Handler)
这个方法的第二个参数,传入的是 engine
,我们继续来看看 ListenAndServe
方法的具体代码:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
server.ListenAndServe()
已经是服务监听并启动的方法,所以关键点在 Handler
这里:
// A Handler responds to an HTTP request. // ... type Handler interface { ServeHTTP(ResponseWriter, *Request) }
这是 http
包里的源码注释,其实写得很详细了,注释很多,我直接省略了,感兴趣的可以去阅读一下,不出意外,这个 Handler
是一个接口,从 gin.engine
的传入已经可以看出来了吧?注释的意思是这个接口是 处理程序响应HTTP请求 ,可以理解为,实现了这个接口,就可以接收所有的请求并进行处理。
因此 gin
框架的入口就是从这里开始, gin engine
实现了 ServeHTTP
,然后接管所有的请求走 gin 框架的处理方式。细心的读者应该能发现,原生的 http web
服务传入了 nil
,事实上是 http
库也有一个默认的请求处理器,感兴趣的读者可以去仔细阅读研究一下 go
官方团队的请求处理器实现哦~
那我们继续来看下 gin
框架是怎么实现这个 ServeHTTP
方法的:
// gin.go // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) // 从池里获取资源处理请求 c.writermem.reset(w) // 重置资源的 ResponseWriter c.Request = req // 赋值请求给 context c.reset() engine.handleHTTPRequest(c) // 将资源信息给到 handler 进一步处理 engine.pool.Put(c) // 请求处理结束,将资源放回池子 }
可以看到实现并不难,代码的含义我已经写上了注释,这里的重点是 Context
,但不是这一篇文章的重点,我放在这一系列的后面进行讲解。
我们只需要知道, gin
也是实现了 Handler
接口,所以可以将请求按 gin
的处理方式进行处理,也就是我们可以使用 gin
来做 web
服务框架的起点。
gin.Default()
接下来进去 Default
函数去看具体实现,代码如下:
// gin.go // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { debugPrintWARNINGDefault() // 忽略 engine := New() engine.Use(Logger(), Recovery()) // 暂时忽略 return engine }
其实单单看函数名,已经知道这是构造默认的 gin engine
,可以看到 engine
是通过 New()
方法得到的,我们选忽略第一行和第三行。
// gin.go func New() *Engine { debugPrintWARNINGNew() // 忽略 engine := &Engine{ RouterGroup: RouterGroup{ // 路由组,后面再分析 Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, // bool,是否为默认处理 engine UseRawPath: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine // 重点,将 engine 重新赋值给路由组对象中的 engine engine.pool.New = func() interface{} { // 重点,资源池 return engine.allocateContext() } return engine }
到这里,我们不用看 engine
结构的具体定义,也已经看出来比较多的信息了,主要用路由组和资源池,这两个都可以分别展开写一篇文章,由于篇幅有限,这里就先不介绍了。
值得注意的是这里面有两个地方很巧妙:
engine.RouterGroup.engine = engine
很明显,路由组 RouterGroup
中还有个 engine
的指针对象,为什么要这么设计呢?读者们可以思考一下。
engine.pool.New = func() interface{} { // 对象池 return engine.allocateContext() }
看下 engine.allocateContext()
方法:
func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} }
可以看到 engine
中包含了 pool
对象池,这个对象池是对 gin.Context
的重用,进一步减少开销,关于 sync.pool
对象池我就不在这里细说了, 后续再更新关于 sync.pool
的文章。
engine
最后再来看看 engine
的结构:
type Engine struct { // 路由组 RouterGroup // 如果true,当前路由匹配失败但将路径最后的 / 去掉时匹配成功时自动匹配后者 // 比如:请求是 /foo/ 但没有命中,而存在 /foo, // 对get method请求,客户端会被301重定向到 /foo // 对于其他method请求,客户端会被307重定向到 /foo RedirectTrailingSlash bool // 如果true,在没有处理者被注册来处理当前请求时router将尝试修复当前请求路径 // 逻辑为: // - 移除前面的 ../ 或者 // // - 对新的路径进行大小写不敏感的查询 // 如果找到了处理者,请求会被301或307重定向 // 比如: /FOO 和 /..//FOO 会被重定向到 /foo // RedirectTrailingSlash 参数和这个参数独立 RedirectFixedPath bool // 如果true,当路由没有被命中时,去检查是否有其他method命中 // 如果命中,响应405 (Method Not Allowed) // 如果没有命中,请求将由 NotFound handler 来处理 HandleMethodNotAllowed bool ForwardedByClientIP bool // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool // 如果true, url.RawPath 会被用来查找参数 UseRawPath bool // 如果true, path value 会被保留 // 如果 UseRawPath是false(默认),UnescapePathValues为true // url.Path会被保留并使用 UnescapePathValues bool // 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 //每个http method对应一棵树 trees methodTrees }
上面提到过 ServeHTTP
这个方法,其中 engine.handleHTTPRequest(c)
这行代码就是具体的处理操作。
可以看到 engine
的结构中有这么一个字段 RedirectTrailingSlash
,在 Default()
初始化方法中为 true
,我对此比较感兴趣,大家也可以根据注释的意思来测试一下,最终会走到下面的代码中:
func (engine *Engine) handleHTTPRequest(c *Context) { …… if httpMethod != "CONNECT" && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { // 这里就是尝试纠正请求路径的函数 redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } …… }
总结一下
上图就是本文的核心了,可以结合图来理解一下 gin
启动的过程及设计,下一篇会将 gin
的路由,敬请期待~
本系列 “拆轮子系列:gin 框架” 的第一篇就到这里了,这么通读下来,发现 gin
框架的设计和实现真的太棒了,简洁清晰,又不失巧妙,很适合大家也去阅读学习一下,墙裂推荐!!!
欢迎关注我们的微信公众号,每天学习Go知识
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK