内容简介:从官方提供的首先是从上面可以看出,
初始化 Engine 对象
从官方提供的 demo
代码来逐行解析 gin
源码架构
r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":9999")
首先是 gin.Default()
,如下
func Default() *Engine { // debug 信息 debugPrintWARNINGDefault() // 返回了一个 Engine 结构体对象 engine := New() // engine 使用两个全局中间件 // 第一个是 logger,核心功能是通过 fmt.Fprint(out, formatter(param)) 来输出日志 // 第二个是 recovery,核心功能是通过 defer func() { if err := recover(); err != nil { ... } } 来处理 panic 错误 // 并将 panic 类型的错误转化为 500 服务器错误抛出 c.AbortWithStatus(http.StatusInternalServerError) // go 的 recover() 函数可以捕获抛出的 panic 类型错误 engine.Use(Logger(), Recovery()) return engine } // New 函数返回一个默认配置的 Engine 结构体对象 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, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } // 依赖注入 engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } // 注册全局中间件 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { // 注册中间件 engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { // 将全局中间件添加到 group.Handlers group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
从上面可以看出, gin.Default
其实是返回了一个 gin
自定义的 Engine
结构体实例,并添加了两个默认中间件 logger
和 recovery
分别用于记录日志和捕获 panic
错误,随后的操作都是对这个 Engine
实例的操作。
添加路由
接下来逐行解析路由挂载,首先是 r.GET("/ping", ...gin.HandlerFunc)
// Engine.GET 直接访问到 RouterGroup.GET 是因为 /** type Engine struct { RouterGroup ... } */ // RouterGroup 通过结构体语法直接挂载在了 Engine 结构体下,所以可以直接通过 Engine 访问 RouterGroup 的方法 func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { // http.MethodGet 是一个字符串常量 "GET" return group.handle(http.MethodGet, relativePath, handlers) } // group.handle func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { // 拼接绝对路径 absolutePath := group.calculateAbsolutePath(relativePath) // 将所有的 HandlerFunc 组合在一起(其中包括中间件和主函数) handlers = group.combineHandlers(handlers) // 为当前方法和路由添加处理函数集合(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) // 最后返回 Engine 对象,可进行链式操作 return group.returnObj() } // 合并 handler func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) // 最大数量为 63 if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) // 合并全局中间件(group.Handlers)和当前路由中间件和处理函数(handlers) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers } // 注册路由 func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) // 获取该方法的节点缓存 root := engine.trees.get(method) if root == nil { // 新建节点 root = new(node) // 处理所有路径的函数 root.fullPath = "/" // 在树中添加该节点 engine.trees = append(engine.trees, methodTree{method: method, root: root}) } // 为 root 节点添加子节点 node root.addRoute(path, handlers) } // node 结构 type node struct { path string indices string // 每种方法(如 GET)是一个父节点 // 每个路径(如 /ping)都是一个子节点,通过 addRoute 添加到 children 中 children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool fullPath string }
gin
对于路由定义的处理分为两步:
-
将全局中间件和单路由中间件及处理函数进行合并,得到一个
gin.HandlerFunc
数组; -
将这个路由信息生成一个
node
节点,挂载在Engine
的trees
节点树中,不同的方法(如 GET、PUT...)成为父树,而不同的路径(如 /foo、/bar)则是子树,将handlers
及其他信息挂载在这个node
节点中,以便在未来使用。
启动应用
从 r.Run(":9999")
来进行解析,然后再回到 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})}
。
func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() // 处理地址 address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) // http.ListenAndServe 监听端口,传入 engine // http.ListenAndServe 的处理函数需要实现 ServeHTTP 方法,所以我们需要看 Engine 的 ServeHTTP 方法 err = http.ListenAndServe(address, engine) return } func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 对 *gin.Context 进行初始化 // *gin.Context 是 gin 的核心,太庞大了,这里不做展开 c := engine.pool.Get().(*Context) // 注入 http.ResponseWriter 和 *http.Request 到 *gin.Context 中 c.writermem.reset(w) c.Request = req // 每一次网络请求都会调用 c.reset() 对 *gin.Context 进行重置 c.reset() // 处理网络请求,进行响应(也是在这里做最终的路由匹配) engine.handleHTTPRequest(c) // 在对象池进行缓存,减少创建开销 engine.pool.Put(c) } // 核心处理函数 func (engine *Engine) handleHTTPRequest(c *Context) { // 从注入的依赖中取出请求方法及请求路径 httpMethod := c.Request.Method rPath := c.Request.URL.Path //... t := engine.trees for i, tl := 0, len(t); i < tl; i++ { // 判断请求方法是否能匹配到节点 if t[i].method != httpMethod { continue } root := t[i].root // 匹配路由,到这里就比较简单了,这里不做展开 // value 中包含了 handlers value := root.getValue(rPath, c.Params, unescape) if value.handlers != nil { c.handlers = value.handlers c.Params = value.params c.fullPath = value.fullPath // c.Next() 就是遍历 handlers,按顺序依次执行 // 也就完成了所有中间件及最终响应函数的执行,该函数在下面有展开 c.Next() c.writermem.WriteHeaderNow() return } //... } // 无匹配项,404 错误处理 serveError(c, http.StatusNotFound, default404Body) } func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } } // c.Next() 最终执行了 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})} // c.JSON func (c *Context) JSON(code int, obj interface{}) { // 调用 render c.Render(code, render.JSON{Data: obj}) } func (c *Context) Render(code int, r render.Render) { c.Status(code) // ... // 最终调用 render.JSON.Render 方法中的 WriteJSON 方法响应结果 if err := r.Render(c.Writer); err != nil { panic(err) } } func WriteJSON(w http.ResponseWriter, obj interface{}) error { // 写入对应的 header 头部 writeContentType(w, jsonContentType) // 在 io.Writer 中写入对应的 JSON 内容 // 这里的 io.Writer 对应的就是 http.ResponseWriter encoder := json.NewEncoder(w) err := encoder.Encode(&obj) return err }
我们最后来梳理一遍, r.Run(":9999")
启动了一个 http
服务,监听了指定端口,然后将端口的所有请求交给 Engine
处理。
Engine
之所以有处理请求的能力,是因为实现了 http.Handler
接口,包含 ServeHTTP
方法,所有的请求就会交由 Engine
的 ServeHTTP
处理。
Engine
的 ServeHTTP
方法包装了一个 *gin.Context
对象,将这个对象传入每个 gin.HandlerFunc
中,然后调用所有的 handlers
,完成对中间件及最终响应函数的调用。
至此, gin
的主流程已经梳理完毕,接下来的文章是对 gin
的一些 API
的深入梳理,欢迎关注。
以上所述就是小编给大家介绍的《Gin 源码阅读(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【源码阅读】AndPermission源码阅读
- 【源码阅读】Gson源码阅读
- 如何阅读Java源码 ,阅读java的真实体会
- 我的源码阅读之路:redux源码剖析
- JDK源码阅读(六):HashMap源码分析
- 如何阅读源码?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
来吧!带你玩转 Excel VBA
罗刚君、杨嘉恺 / 电子工业出版社 / 2013-7 / 85.00元
本书旨在普及Excel VBA 基础理论,以及通过VBA 的高级应用扩展Excel 的功能,提升读者的制表效率,解决工作中的疑难,同时亦可借此开发商业插件。 本书主要分为操作自动化引言篇、入门篇、进阶篇和疑难解答篇,覆盖从入门到提高的所有内容,以满足不同层次的读者需求。其中操作自动化引言篇简述了操作自动化的需求与方式,借此引出VBA 入门篇。VBA 入门篇包含第2 章到第13 章,主要介绍了......一起来看看 《来吧!带你玩转 Excel VBA》 这本书的介绍吧!