Golang源码学习----gin框架简单阅读

Golang源码学习----gin框架简单阅读,第1张

一、热加载
    go get github.com/pilu/fresh
    快速编译,省去了每次手动go run

二、gin特点
    轻量级、运行速度快,性能、高效
    擅长API接口的高并发,项目规模不大,业务简单


三、Engine启动器
    Engine是框架的实例,使用NEW()或着Default()来创建。
    使用gin实际上就是使用engine的方法。

    其中
        engine.trees    !!!负责存储路由和handle方法的映射,采用类似前缀树的结构
        RouterGroup     是一个结构,其中的Handlers存储着所有中间件


    type Engine struct {
	    RouterGroup         ####
            ...
	    trees            methodTrees        ####
            ...
    }


四、Run
    · Run:
            连接路由到http。服务器开启监听和服务HTTP请求。
            这是http.ListenAndServe(Addr,engine)
            ListenAndServe监听TCP网络地址addr,然后用engine调用Serve来处理传入连接上的请求。
        ListenAndServe:
            监听TCP网络地址addr,然后调用带有handler的server来处理传入连接的请求。
                handler是一个带有ServeHTTP的方法的接口
            ServeHTTP:
                请求到达后,调用接口。
                创建上下文对象-->初始化上下文对象-->处理请求-->回收上下文对象
                处理请求是用的 engine.heandleHTTPRequest方法:(redix树就是engine结构中的一个前缀树,可以快速找到请求处理方法)
                    获取请求方式对应的radix树-->
                    得到请求路径匹配的radix树节点,将节点的路由处理器填充到上下文context-->
                    执行上下文context的next方法处理请求-->
                    如果请求没有匹配到或者请求方法不循序,响应对应错误
        可以看到engine本质上是路由处理器,内部定义了路由处理的各种规则,底层还是go的net/http包。
    

五、gin.Context.Next()
    gin.Context 上下文对象,负责处理 请求和回应 ,其中handlers是存储处理请求时中间件的处理方法
    利用engine.pool.New创建gin.context对象,内部采用sync.pool减少gin.context实例化带来的内存消耗。

    Next()方法实际上是逐个调用处理器的请求,而中间件也是处理器类型,所以也可以通过c.Next()来工作。
        用c.index找到对应的处理器
    next再中间件内部使用,它执行调用处理程序内部链中挂起处理程序。
    next内部逻辑:
        1.指向要执行的中间件。初始值是-1.
        2.遍历所有的处理器,依次调用用它们来处理请求

    gin.context的终止:
        调用gin.context.abort()方法直接把index指到63,也就是路由处理的器的最大数量,实现终止处理器调用的功能。


六、中间件  
    通过routergroup.use来注册中间键
    因为engine中也集成了routergroup,所以可以使用engine.use()注册中间件。

    use():
        往RouterGroup.hanlers中追加写入中间件。
        这说明了会按照添加顺序进行执行。
    func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	    group.Handlers = append(group.Handlers, middleware...)
	    return group.returnObj()
    }


    · 注册中间件:
        gin高性能主要依靠engine.tree,每一个节点的内容类似于一个key-value的字典树,value是一个[]handlerFunc;
        里面存储的是按顺序执行的中间件和handle控制器方法!!!
    
    · 注册全局中间件:
        engine.use():
        调用RouterGroup.Use()往RouterGroup.Handlers中写入记录。
        使用一个全局中间件连接到路由,通过use()附加的中间件包含在每一个请求的处理程序链中。即使是404、405、静态文件....

    · 注册路由组中间件:
        通过Group()方法返回一个RouterGroup。
        返回的routerGroup是engine的routergroup的模板,可以节省填写路径和中间件
        用 group.use() 写入分组


七、Default
    · Default:
        返回一个已经附加Logger和Recovery中间件的Engine实例。
        	engine := New()
	        engine.Use(Logger(), Recovery())
        可以看到default就是调用了gin.New(),添加了Logger和Recovery中间件
        Logger:日志写入的中间件
        Recovery:错误处理,遇到panics时按照设置的handle方法处理

    
    · gin.New()
        直接使用gin.New()是初始化一个干净的engine对象
        逻辑:
            1. 初始化一个engine对象
            2. 设置sync.poold的新建context对象函数
    
    · 初始化engine:
        1. routerGroup:设置了根路径
        2. RedirectTrailingSlash:自动把/foo/重定向到/foo,即可以多打一个斜杠
           RedirectFixedPath:是否允许再找不到目标路径时,将/FOO和/../foo这种错误路径自动修复
           HandleMethodNotAllowed:是否自动适配请求方式,如用post方法请求get,是否自动转用get请求
        3. trees 创建容量为9的redix树切片,对应9种请求方法
    

    func New() *Engine {
	    debugPrintWARNINGNew()
	    engine := &Engine{
	    	RouterGroup: RouterGroup{
	    		Handlers: nil,
	    		basePath: "/",      
	    		root:     true,     # 为true代表该路由组是根节点    
	    	},
	    	FuncMap:                template.FuncMap{},
	    	RedirectTrailingSlash:  true,
	    	RedirectFixedPath:      false,
    		HandleMethodNotAllowed: false,

    		ForwardedByClientIP:    true,
	    	RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
	    	TrustedPlatform:        defaultPlatform,
	    	UseRawPath:             false,
	    	RemoveExtraSlash:       false,
	    	UnescapePathValues:     true,

	    	MaxMultipartMemory:     defaultMultipartMemory,

	    	trees:                  make(methodTrees, 0, 9),

	    	delims:                 render.Delims{Left: "{{", Right: "}}"},
	    	secureJSONPrefix:       "while(1);",
	    	trustedProxies:         []string{"0.0.0.0/0"},
	    	trustedCIDRs:           defaultTrustedCIDRs,
	    }
	    engine.RouterGroup.engine = engine
	    engine.pool.New = func() interface{} {
	    	return engine.allocateContext()
	    }
	    return engine
    }


八、Group
    group := e.Group("/imgroup")
    创建一个路由分组
        func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	        return &RouterGroup{
		        Handlers: group.combineHandlers(handlers),      #继承父handlers的同时加上新传入handler
		        basePath: group.calculateAbsolutePath(relativePath),    ##设置路径
		        engine:   group.engine,     #所属engine
	        }
        }

    combineHandlers:判断handlers数量是否超出最大62,把engine的handles深拷贝copy下来,把传入的handles也copy下来,然后返回被copy初始化的handlers


九、注册路由
    RouterGroup.get()注册一个get路由
        func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	        return group.handle(http.MethodGet, relativePath, handlers)
        }
    可以发现get()方法其实就是调用了一个group.handle

    group.handle():
        处理注册一个新的请求句柄和中间件与给定的路径和方法。  
        1. 把传入的路由地址转码
        2. 将handle添加到handles
        3. group.engine.addRoute(httpMethod, absolutePath, handlers)

        addRoute:
            1.根据相对路径和routergroup路径计算绝对路径
            2.添加路由,(radix树添加在最后)


十、gin.context:
    gin的context和go的原生context不是一回事
    gin上下文方法分为几个大类:
        创建、流程控制、错误控制、元数据管理、请求数据、响应渲染、内容协商
        方法按照分类
    gin.context.copy():
        copy返回上下文的副本。
        拷贝上下文传递给goroutine时,必须使用此选项。
        防止被回收

    ctx.Query()
        获取url的请求key-value参数
    ctx.PostForm()
        获取post值key-value
    ctx.ShouldBindWith()
        监听绑定
    ctx.HTML()
        渲染成html
    ctx.Param()
        获取restful这种的参数   /user/:userid


十一、注意点
    1. context的创建时,使用sync.pool来复用内存。
    2. gin底层是net/http包,gin的本质是以恶搞路由处理器
    3. 每个请求方法都有一颗radix树(get/post...)
    4. 添加中间件的过程就是切片追加元素的过程,也决定了中间件会按照添加时间顺序来执行
    5. 可以为不同路由组添加不同的中间件
    6. 路由组本质就是一个模板,维护了路径前缀、上级路由中间件等信息,让用户省去了重复配置相同前缀和中间件的 *** 作
    7. 新路由组集成父路由组的所有处理器
    8. 如果context需要被并发使用,需要使用context副本,拷贝context需要使用gin.copy()来使用
    9. 拷贝上下文必须使用gin.context.copy()


十二、总结
    1. gin框架路由原理:
        gin是一个高性能路由分发器,负责将不同方法的多个路径注册到各个handle函数;
        当收到请求时,负责快速查找请求的路径是否有对应的处理函数,并且进行下一步业务逻辑处理。
        通过redix tree来进行高效的路径查询,gin对每一种http方法都生成一颗radix树

    2. 注册路由:    router.get("/hello",func (*gin.context))
        RouterGroup.Get/POST...都是对routergroup.handle的调用,只是传入的httpMethod不同;
        httpMethod将传入的相对路径通过和router的路径计算得到了绝对路径;
        将传入的handel合并到router的handels切片,因为是追加添加,所以是顺序进行的,前面是router之前use的中间件;
        调用addRouter方法调用gin.tree.node.addrouter()添加路由
        通过method找到对应的radix树,在对应的radix树中插入了path-handle的key-value值,方便快速查找

    3. 路由的匹配   engine.run(addr string)
        Gin内部实际上是调用了Go自带库的net/http库中的ListenAndServe函数,
        http.ListenAndServe(addr, handler)的handler是engine自带实现了的,在gin中调用ListenAndServe也是传入的engine;
        gin.Handler中实现了ServeHTTP方法;
        首先从engine的对象池中pool获取一个上下文对象并对其的属性进行重置;
        然后将context重新放回engine的context池中;
        然后以该context调用handleHTTPRequest再通过getvalue找到路由对请求进行处理,最后再把context放入engine中。

   
十三、总结精简
    服务启动:  main.go、获取engine对象、注册路由、启动服务、http.ListenAndServe()
    路由注册:RouterGroup.GET/POST..、RG.handle()、RG.addrouter、tree.addrouter
    请求处理:engine.run、http.ListenAndServe、engine.ServeHTTP、engine.pool.get、engine.handleHTTPRequest、c.next执行、engine.pool.put

十四、Tarix
    挖个坑,暂时还没了解gin中radix的具体实现。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/886863.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-14
下一篇 2022-05-14

发表评论

登录后才能评论

评论列表(0条)

保存