gin框架基础

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
}
上一篇:docker中安装Redis


下一篇:gin框架源码解析