注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
最近打算开始学习一下 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" ) })
// Listen and server on 0.0.0.0:8080 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() // 在这儿必须在ping,前加上/,不然会导致panic r.GET("/ping" , func (c *gin.Context) { // String 方法接受两个参数,但是实例只写了一个 c.String(http.StatusOK,"pong" ) })
// Listen and server on 0.0.0.0:8080 r.Run(":80" ) }
gin.go
1 2 3 4 5 6 7 8 9 10 11 // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New () *Engine { engine := &Engine{} engine.RouterGroup = &RouterGroup{nil , "" , nil , engine} engine.router = httprouter.New() // NotFound 是一个http.Handler的接口,但是源码当中赋值了一个方法给他 engine.router.NotFound = engine // engine.router.NotFound = engine.handle404 return engine }
好了修改完成之后就可以运行, go run main.go成功运行了,但是还有一个 bug,只能访问一次,就会因为stack overflow退出
查看一下gin.go, ServeHTTP可以发现,gin 是直接调用了httprouter的ServeHTTP方法
1 2 3 4 // ServeHTTP makes the router implement the http.Handler interface. 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 // Handle 404 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 // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New () *Engine { engine := &Engine{} engine.RouterGroup = &RouterGroup{nil , "" , nil , engine} engine.router = httprouter.New() // NotFound 是一个http.Handler的接口,但是源码当中赋值了一个方法给他 // 注释掉即可 // engine.router.NotFound = engine.handle404 return engine }
go run main.go成功运行,就没有问题了
跑起来之后就具体看看源码,初始版本的 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是gin当中最为重要的一部分 // 它用于在中间件当中传递变量,管理流程。例如接受json请求,并返回json // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. Context struct { // ServeHTTP 第二个参数,请求体 Req *http.Request // ServeHTTP 第一次参数,响应 Writer http.ResponseWriter // 可以设置的值 Keys map [string ]interface {} // 错误信息 Errors []ErrorMsg // 请求参数 Params httprouter.Params // = 中间件 + 请求处理函数(最后一个) handlers []HandlerFunc // Engine 实例 engine *Engine // 当前处理到的Handler下标 index int8 }// 下一个中间件 Next()// 终止处理,直接返回 Abort(code int )// 添加错误信息,并且终止处理 Fail(code int , err error)// 添加错误信息 Error(err error, meta interface {})
// 给Context.Keys添加值 Set(key string , item interface {})// 获取Context.Keys的值,如果不存在会导致panic Get(key string ) interface {}
// 将请求体的参数作为json解析 ParseBody(item interface {}) error// 同ParseBody,但是如果不是一个可解析的json会调用Fail(400)终止请求 EnsureBody(item interface {}) bool
// 下面是和返回相关的函数,code 参数均表示http status
// 返回json JSON(code int , obj interface {})// 返回xml XML(code int , obj interface {})// HTML模板渲染,使用golang标准库的模板库 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用于管理路由,一个RouterGroup和一个前缀以及一组中间件关联 // Used internally to configure router, a RouterGroup is associated with a prefix // and an array of handlers (middlewares) RouterGroup struct { // 中间件 Handlers []HandlerFunc // 路径前缀 prefix string parent *RouterGroup // Engine 实例 engine *Engine }// 新建一个Context,用于传递这个路由组的数据 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)// 调用Handle方法注册一个POST路由 POST(path string , handlers ...HandlerFunc)// 调用Handle方法注册一个GET路由 GET(path string , handlers ...HandlerFunc)// 调用Handle方法注册一个DELETE路由 DELETE(path string , handlers ...HandlerFunc)// 调用Handle方法注册一个PATCH路由 PATCH(path string , handlers ...HandlerFunc)// 调用Handle方法注册一个PUT路由 PUT(path string , handlers ...HandlerFunc)// 组合中间件,将传入的Handlers放在已有的Handlers后面 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 // 用于表示Web框架,包含了fast HTTProuter和一个全局的中间件列表 // Represents the web framework, it wrappers the blazing fast httprouter multiplexer and a list of global middlewares. Engine struct { // 路由组 *RouterGroup // 404 处理 handlers404 []HandlerFunc // http router 实例 router *httprouter.Router // 模板 HTMLTemplates *template.Template }// 加载HTML模板 LoadHTMLTemplates(pattern string )// 设置404方法 NotFound404(handlers ...HandlerFunc)// 默认的404方法,但是这个版本并没有使用上 handle404(w http.ResponseWriter, req *http.Request)// 保存文件 ServeFiles(path string , root http.FileSystem)// 实现http.Handler接口 ServeHTTP(w http.ResponseWriter, req *http.Request)// 调用http.ListenAndServe启动服务器 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 从头开始执行所有的中间件以及注册路由时注册的请求处理函数从请求处理函数中返回信息 关注我获取更新猜你喜欢