GIN框架解析与源码分析(一)

本文主要对GIN框架的两个核心概念(router和context)进行代码解读。

Router

1、支持POST,GET,PUT等多种方法,实现如下:

type methodTree struct {
    method string
	root   *node
}

type methodTrees []methodTree

func (trees methodTrees) get(method string) *node {
	for _, tree := range trees {
		if tree.method == method {
			return tree.root
		}
	}
	return nil
}

2、使用前缀树实现的router树,结点(node)的实现如下:

type node struct {
	path      string // uri的路径
	indices   string // 子节点的首字母集合
	wildChild bool // 是否是通配字段
	nType     nodeType // 结点类型
	priority  uint32 // 优先级
	children  []*node // 子结点
	handlers  HandlersChain // 绑定的处理函数
	fullPath  string
}

这里有一处实现巧妙的地方。作者通过priority来实现子节点的优先级,优先级越高的子节点indices字段中出现的位置越靠前,实现如下:

func (n *node) addChild(child *node) {
	if n.wildChild && len(n.children) > 0 {
		wildcardChild := n.children[len(n.children)-1]
		n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
	} else {
		n.children = append(n.children, child)
	}
}

优先级的计算方式为出现一个相同path首字母的子结点,优先级加一。我理解作者是基于这样的假定,如果注册的path越集中,则请求来的path更大概率会出现在这些集中的node中。

3、在前缀树的基础上,实现两种通配字段,使用起来非常的方便,举例如下:

3.1 通配符’:’: 能匹配两个‘/’中的字符串

router.GET("/user/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})

它能匹配/user/john,但不能匹配/user/ /user /user/john/smith

3.2 通配符’*’: 能匹配‘/’之后所有的字符串,它之后不允许再出现别的通配符

router.GET("/user/*name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})
它能匹配/user/john 或 /user/john/smith 或 /user/,但不能匹配/user/ 或 /user 或 /user/john/smith
使用通配字段可以实现更加简洁的uri,比如/user/:id,可以直接将用户的id写在path里,而不必向通常那样写成这样 /user?id=xxx
代码实现在addRoute和getValue函数中,比较晦涩(细节很多),可以找一些case自己走一遍,这样能更快的理解代码。另外作者写了很多单元测试用例,通过这些用例也可以更容易理解函数的功能。

Context

type Context struct {
	writermem responseWriter // 读写请求
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params // 请求的参数
	handlers HandlersChain // 处理函数链
	index    int8 // 当前正在处理函数的序号
	fullPath string
	engine *Engine
	params *Params // 请求的query参数
	mu sync.RWMutex
	Keys map[string]interface{} // 公共参数,middleware可以设置
	Errors errorMsgs // 错误消息组
	Accepted []string // 接受请求格式类型
	queryCache url.Values // 请求参数缓存
	formCache url.Values // 请求体缓存
	sameSite http.SameSite
}

1index

该值表示处理到HandlersChain的第几个函数。正常情况下是顺序进行处理,当处理完成后,index值加1,实现如下:

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

如果出现错误的情况,前面的处理函数(或者叫中间件)可以中断本次请求,直接返回错误信息,实现如下:

func (c *Context) Abort() {
	c.index = abortIndex
}

可见是通过index的值来控制服务继续处理还是提前终止的。

这样的实现方式允许调用者灵活的编写处理函数,如下:

router.Use(func(c *Context) {
		signature += "A"
		c.Next()
		signature += "B"
	})

signature += "B"这一语句会在所有处理函数调用完成后再执行。

2Keys

所有处理函数均可读写该变量,后面的处理函数可以读取前面处理函数写的值。

实现如下:

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
}

比如我们有一个中间件专门来验证用户信息,当核验成功后,会把用户的一些基本信息写入keys中,这样后面的处理函数就可以直接拿到这些信息了。

3. Binding

框架支持直接处理json、xml等格式的请求体。并且对其字段进行验证(如未识别的字段名),如下:

type Login struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

binding:"required"`表示这个字段是必须的,否则会报错(直接)。它的申明表明同时支持jsonxml两种格式,gin框架支持通过content type推断请求的格式体。

另一个例子如下:

type Booking struct {
	CheckIn  time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"`
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
它指定的时间字段的格式,同时要求check_out字段的时间一定在check_in字段之后。
默认使用https://github.com/go-playground/validator/tree/v8.18.2 进行请求验证,Gin框架同时支持自定义的验证函数,如下:
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
		today := time.Now()
		if today.After(date) {
			return false
		}
	}
	return true
}
4. Render

Binding是gin框架对于用户请求解析的支持,render则是对于返回结果的支持。首先同样支持多类型的结果返回,其次能完成status code和content type的填写,最后能由不同的返回格式序列化返回结果。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值