一、热加载
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的具体实现。