gin框架基础
不使用web框架
request和response
func main() {
http.HandleFunc("/hello", defaultHello)
}
// HTTP请求的所有的信息,比如请求地址、Header和Body等信息;第一个参数是 ResponseWriter ,利用 ResponseWriter 可以构造针对该请求的响应。
func defaultHello(w http.ResponseWriter, req *http.Request) {
for k,v := range req.Header {
fmt.Println(k," --- ",v)
}
}
添加Context
接管 request
框架能力开始形成...
// 使用Context接管http的request
type Context struct {
Request *http.Request
Writer ResponseWriter
}
// 引入Engin接管ServeHTTP方法
type Engine struct{
// ...
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := newContext(w, req)
engine.router.handle(c)
}
Gin的主要结构体
// demo
func main() {
r := gin.New()
r.Use(gin.Logger(),gin.Recovery())
r.GET("/hello", hello)
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2组使用中间件
{
v2.GET("/hello/:name", func(c *gin.Context) {
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.FullPath())
})
}
r.Run(":1323")
}
// engine is a framework instance.类比Echo结构体
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedProxies: []string{"0.0.0.0/0"},
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 Default() *Engine {
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
Engine
// Engine作为最顶层的分组,也就是说Engine拥有RouterGroup所有的能力。
type Engine struct {
RouterGroup
pool sync.Pool
trees methodTrees
maxParams uint16
}
RouterGroup
// 管理路由,一个prefix和一组handler/middleware
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
Context
type Context struct {
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8 // 初始-1,表示无中间件
fullPath string
engine *Engine
params *Params
// This mutex protect Keys map
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
}
Gin路由
路由为每种请求方法管理一颗单独的树,而不是每个节点都放在一个单独的树,从而节省路由查找时间。
注册路由主要是addRoute和insertChild函数,相应的中间件函数会一起整合到节点上,形成前缀树。
r.GET("/hello", handlerFun)
r.GET("/hello", handlerFun1,handlerFun2,handlerFun3) // 可多个函数/中间件
// routergroup.go
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers) // 整合handlers
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
路由分组
以相同的前缀分组,支持分组嵌套,在分组上添加中间件,也会作用在子分组,子分组还可以拥有自己的中间件。
// 路由分组管理
v1 := s.Group("/v1")
{
v1.GET("/hello", gindonothing)
}
路由匹配
// gin.go
func (engine *Engine) handleHTTPRequest(c *Context) {
// ...
// 根据请求方法找到对应的路由树
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// 在路由树中根据path查找
value := root.getValue(rPath, c.Params, unescape)
// 这里将此节点的一些参数给到context
if value.handlers != nil {
c.handlers = value.handlers // 中间件给到Context
c.Params = value.params
c.fullPath = value.fullPath
c.Next() // 执行函数链条
c.writermem.WriteHeaderNow()
return
}
// ...
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
Gin中间件
插入点在哪
中间件是应用在RouterGroup
上的,应用在最顶层的 Group,相当于作用于全局,所有的请求都会被中间件处理。
那为什么不作用在每一条路由规则上呢?作用在某条路由规则,那还不如用户直接在 Handler 中调用直观。
r := gin.New()
r.Use(gin.Logger())
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2 group middleware
{
v2.GET("/hello/:name", func(c *gin.Context) {
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.FullPath())
})
}
func onlyForV2() gin.HandlerFunc {
return func(c *gin.Context) {
// Start timer
t := time.Now()
// if a server error occurred
c.String(500, "Internal Server Error")
// Calculate resolution time
log.Printf("%s in %v for group v2", c.Request.RequestURI, time.Since(t))
}
}
中间件的来历
当接收到请求后,匹配路由,该请求的所有信息都保存在Context
中。中间件也不例外,接收到请求后,应查找所有应作用于该路由的中间件,保存在Context
中,依次进行调用。为什么依次调用后,还需要在Context
中保存呢?因为在设计中,中间件不仅作用在处理流程前,也可以作用在处理流程后,即在用户定义的 Handler 处理完毕后,还可以执行剩下的操作。
// 添加中间件,先执行上层路由中的中间件,再执行当前路由组的中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// 路由函数和中间件函数组合在一起
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers) // 将处理请求的函数与中间件函数结合
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
const abortIndex int8 = math.MaxInt8 / 2
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) { // 这里有一个最大限制
panic("too many handlers")
}
// Handler合并
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
中间件的执行
只有需要在request前后都执行才需要使用c.Next()方法。
index
是记录当前执行到第几个中间件,当在中间件中调用Next
方法时,控制权交给了下一个中间件,直到调用到最后一个中间件,然后再从后往前,调用每个中间件在Next
方法之后定义的部分。如果我们将用户在映射路由时定义的Handler
添加到c.handlers
列表中,结果会怎么样呢?
// gin.go ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
// ...
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() // 执行函数链条
c.writermem.WriteHeaderNow()
return
}
}
func (c *Context) Next() {
c.index++
s := len(c.handlers)
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
假设我们应用了中间件 A 和 B,和路由映射的 Handler。
func A(c *Context) {
part1
c.Next()
part2
}
func B(c *Context) {
part3
c.Next()
part4
}
c.handlers
是这样的[A, B, Handler],c.index
初始化为-1。调用c.Next()
,接下来的流程是这样的:
- c.index++,c.index 变为 0
- 0 < 3,调用 c.handlers[0],即 A
- 执行 part1,调用 c.Next()
- c.index++,c.index 变为 1
- 1 < 3,调用 c.handlers[1],即 B
- 执行 part3,调用 c.Next()
- c.index++,c.index 变为 2
- 2 < 3,调用 c.handlers[2],即Handler
- Handler 调用完毕,返回到 B 中的 part4,执行 part4
- part4 执行完毕,返回到 A 中的 part2,执行 part2
- part2 执行完毕,结束。
一句话说清楚重点,最终的顺序是part1 -> part3 -> Handler -> part 4 -> part2
。恰恰满足了我们对中间件的要求,接下来看调用部分的代码,就能全部串起来了。
中间件的终止
// 通过调用c.Abort()中断整个调用链条,从当前函数返回
func (c *Context) Abort() {
c.index = abortIndex // 直接将索引置为最大限制值,从而退出循环
}
Context增强
通过context
在中间件之间传递参数
// Set()/Get()函数间传递参数
func (c *Context) Set(key string, value interface{}) {
c.mu.Lock()
if c.Keys == nil {
c.Keys = make(map[string]interface{})
}
c.Keys[key] = value
c.mu.Unlock()
}
func (c *Context) Get(key string) (value interface{}, exists bool) {
c.mu.RLock()
value, exists = c.Keys[key]
c.mu.RUnlock()
return
}