注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
最近打算开始学习一下 Gin 的源代码,现在 Gin 已经是一个十分成熟的框架了,代码量也不少,阅读起来还是有一定的难度,所以我打算从 0.1 版本开始阅读学习,一直到最新的一个版本。跟随着 Gin 的源码一步一步的学习成长。
目录结构 Gin 0.1 的代码量十分的少, 主要代码一共也只有五个文件,代码中的注释也比较详细
1 2 3 4 5 6 │ auth.go │ gin.go │ logger.go │ README.md │ recovery.go │ validation.go
跑起来 Gin 0.1 的代码量十分的少,但是还是先从readme
的示例开始说起
首先下面这一段代码是直接跑不起来的,不知道是代码本身的 bug 还是因为 Go 语言的版本变化导致的,首先我们需要修改几个地方
1 2 3 4 5 6 7 8 9 10 11 12 import "github.com/gin-gonic/gin" func main () { r := gin.Default() r.GET("ping" , func (c *gin.Context) { c.String("pong" ) }) r.Run(":80" ) }
第一次修改 main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "github.com/gin-gonic/gin" "net/http" )func main () { r := gin.Default() r.GET("/ping" , func (c *gin.Context) { c.String(http.StatusOK,"pong" ) }) r.Run(":80" ) }
gin.go
1 2 3 4 5 6 7 8 9 10 11 func New () *Engine { engine := &Engine{} engine.RouterGroup = &RouterGroup{nil , "" , nil , engine} engine.router = httprouter.New() engine.router.NotFound = engine return engine }
好了修改完成之后就可以运行, go run main.go
成功运行了,但是还有一个 bug,只能访问一次,就会因为stack overflow
退出
查看一下gin.go
, ServeHTTP
可以发现,gin 是直接调用了httprouter
的ServeHTTP
方法
1 2 3 4 func (engine *Engine) ServeHTTP (w http.ResponseWriter, req *http.Request) { engine.router.ServeHTTP(w, req) }
继续追踪,可以发现在httprouter
的ServeHTTP
方法最后有一段判定 404 的代码,这时候就可以发现这是之前修改gin.go`` engine.router.NotFound = engine
这段代码造成的,由于 Chrome 浏览器访问的时候会尝试访问/favicon.ico
这个文件,然而我们在路由当中并没有定义,此时就是 404,这时候由于之前我们在初始化的时候,给router
传递的NotFound
为engine
,而engine.ServeHTTP
调用了router.ServeHTTP
这时候就造成了无限递归,导致最后退出
1 2 3 4 5 6 if r.NotFound != nil { r.NotFound.ServeHTTP(w, req) } else { http.NotFound(w, req) }
第二次修改 gin.go
1 2 3 4 5 6 7 8 9 10 11 func New () *Engine { engine := &Engine{} engine.RouterGroup = &RouterGroup{nil , "" , nil , engine} engine.router = httprouter.New() return engine }
go run main.go
成功运行,就没有问题了
Gin 源码分析 跑起来之后就具体看看源码,初始版本的 Gin 当中拥有三个比较重要的struct
,也是核心的组成部分
Context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 Context struct { Req *http.Request Writer http.ResponseWriter Keys map [string ]interface {} Errors []ErrorMsg Params httprouter.Params handlers []HandlerFunc engine *Engine index int8 } Next() Abort(code int ) Fail(code int , err error) Error(err error, meta interface {}) Set(key string , item interface {}) Get(key string ) interface {} ParseBody(item interface {}) error EnsureBody(item interface {}) bool JSON(code int , obj interface {}) XML(code int , obj interface {}) HTML(code int , name string , data interface {}) String(code int , msg string ) Data(code int , data []byte )
RouterGroup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 RouterGroup struct { Handlers []HandlerFunc prefix string parent *RouterGroup engine *Engine } createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context Use(middlewares ...HandlerFunc) Group(component string , handlers ...HandlerFunc) *RouterGroup Handle(method, p string , handlers []HandlerFunc) POST(path string , handlers ...HandlerFunc) GET(path string , handlers ...HandlerFunc) DELETE(path string , handlers ...HandlerFunc) PATCH(path string , handlers ...HandlerFunc) PUT(path string , handlers ...HandlerFunc) combineHandlers(handlers []HandlerFunc) []HandlerFunc
Engine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Engine struct { *RouterGroup handlers404 []HandlerFunc router *httprouter.Router HTMLTemplates *template.Template } LoadHTMLTemplates(pattern string ) NotFound404(handlers ...HandlerFunc) handle404(w http.ResponseWriter, req *http.Request) ServeFiles(path string , root http.FileSystem) ServeHTTP(w http.ResponseWriter, req *http.Request) Run(addr string )
0.1 版本的 gin 只是一个极小,极为简单的工具箱,主要提供了一个简单的路由和简单的中间件实现,搞清楚下面这两个问题这个框架的也就明白了。
一个使用了Gin
的 Web 应用,从初始化到启动的流程?
一个请求从接收到返回经历了什么?
应用流程 1.首先创建了一个engine
实例,注册了两个两个基本的中间件
1 gin.Default() -> gin.New() -> engine.Use(Recovery(), Logger())
2.然后使用group.Handle
方法注册路由, 关键代码如下,将路由添加到 http router 的树中,当执行 handler 方法的时候,会创建一个Context
并且从头开始执行
1 2 3 4 group.engine.router.Handle(method, p,func (w http.ResponseWriter, req *http.Request, params httprouter.Params) { group.createContext(w, req, params, handlers).Next() })
3.调用http.ListenAndServe
监听指定端口,启动服务器
请求流程 当服务器收到请求时,会调用之前注册的engine.ServeHTTP
方法,查找路由 engine.ServeHTTP
方法使用了httprouter
的ServeHTTP
方法,这是会通过请求的 path 从已注册的路由树上获取对应的路由,并且执行其 handler 方法,如上所示,handler 方法内部通过创建一个 router group 对应的 Context 从头开始执行所有的中间件以及注册路由时注册的请求处理函数从请求处理函数中返回信息 关注我获取更新 猜你喜欢