前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gin框架核心源码走读

Gin框架核心源码走读

原创
作者头像
王昂
发布2020-01-25 16:10:15
2.9K0
发布2020-01-25 16:10:15
举报
文章被收录于专栏:Andy的技术专栏Andy的技术专栏

| 导语 最近考虑给SCF简单封一层web库,提供cgi的http协议处理、上下文、拦截器、html渲染等能力。很自然就想到了Gin框架,基于golang且框架比较轻量,这里简单把核心源码做个走读笔记

目录

Gin框架简介

  1. 最热门的6个Golang框架
  2. What is Gin?
  3. 文档资料
  4. 功能特性

从示例demo开始

源码文件:/gin.go

  1. 数据结构:type Engine struct
  2. 初始化Engine:New()、Default()
  3. 中间件定义:HandlersChain
  4. 添加路由匹配树:addRoute()
  5. 启动监听:Run()
  6. http请求回调:ServeHTTP()

源码文件:/context.go

  1. 数据结构:type Context struct
  2. 中间件执行流:Next()、Abort()
  3. 参数获取:Param()、Query()、PostForm()、Bind()
  4. 上下文操作:Get()、Set()源

源码文件:/routergroup.go

  1. 数据结构:type RouterGroup struct
  2. 路由注册:GET()、POST()、PUT()…
  3. 路由处理:handle()
  4. 注册中间件:Use()、combineHandlers()

Gin框架简介

最热门的6个Golang框架

go语言web框架
go语言web框架

What is Gin?

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance – up to 40 times faster. If you need smashing performance, get yourself some Gin.

文档资料

功能特性

  • 高性能:基于RadixTree的路由策略,没有使用反射,占用内存也小;上下文Context使用了sync.pool对象池
  • 中间件:提供简单的中间件注册来实现扩展性,请求被一串链式中间件处理后才应答
  • 路由分组:通过路由group,提供方便和全面的http路由组织
  • 参数获取:提供了包括GET/POST/BIND等便捷的获取参数方法
  • 内置渲染:简单实用的JSON、XML和HTML渲染方式
  • 崩溃捕获:捕获http请求的panic并恢复

从示例demo开始

代码语言:txt
复制
package main

import (
	"fmt"
	"net/http"
	"github.com/gin-gonic/gin"
)

/* 中间件:token鉴权 */
func auth(ctx *gin.Context) {
	token := ctx.Query("token")
	if token == "abc" {
		fmt.Println("Auth Succ: token=", token)
		ctx.Next() // 鉴权通过:Next() 执行下个Handler
	} else {
		fmt.Println("Auth Fail: token=", token)
		ctx.Abort() // 鉴权失败:Abort() 停止执行后台的Handler链
		ctx.JSON(200, gin.H{"msg": "auth fail..."})
	}
}

/* 业务Handler: curl "localhost:8080/ping?uid=123&name=jonny&token=abc" */
func pingHandler(ctx *gin.Context) {
	url := ctx.Request.URL                      // 获取url
	uid := ctx.Query("uid")                     // 获取url参数
	name := ctx.DefaultQuery("name", "default") // 获取url参数
	fmt.Println("Query url", url, "uid=", uid, "name=", name)
	ctx.JSON(http.StatusOK, gin.H{"msg": "ping..."}) // 返回Json的Http回包
}

func main() {
	r := gin.Default()           // 创建和初始化默认gin.Engine
	r.Use(auth)                  // 这行代码下面的路由都引入Auth中间件
	r.GET("/ping", pingHandler)  // 将/ping和pingHandler注册到路由trees
	r.Run()
}

这段demo的大致流程是:

  1. gin.Default():初始化gin.Engine,这是gin最核心的struct
  2. r.Use(auth):添加auth中间件,让业务Handler在执行前都会先执行auth
  3. r.GET("/ping", pingHandler) :注册GET路由,有请求框架就会回调pingHandler函数
  4. r.Run():启动监听循环

下面针对这个流程,走读一遍框架内部的核心代码

源码文件:/gin.go

数据结构:type Engine struct

Gin最重要的数据结构就是Engine,由路由管理、上下文、以及一些参数配置组成

代码语言:txt
复制
type Engine struct {
	RouterGroup // 继承RouterGroup,实现路由管理能力
	trees methodTrees // 路由树,加速路由Handler匹配
	pool sync.Pool // 保存Context上下文,用Pool利于复用
	...
}

初始化Engine:New()、Default()

可使用New()创建新的Engine实例,或者使用Default()创建初始化日志和异常捕获的Engine实例

代码语言:txt
复制
func New() *Engine {
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		...
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
	}
	engine.RouterGroup.engine = engine     // 初始化路由管理
	engine.pool.New = func() interface{} { // 初始化Context池
		return engine.allocateContext()
	}
	return engine
}
代码语言:txt
复制
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery()) // 注册日志和异常捕获中间件
	return engine
}

中间件定义:HandlersChain

Gin框架很重要一个概念就是中间件,其实就是注册到Engine的Handler函数链:

代码语言:txt
复制
// 中间件函数:HandlerFunc defines the handler used by gin middleware
type HandlerFunc func(*Context)

// 中间件函数链:HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc

添加路由匹配树:addRoute()

把GET/POST等路由Handler注册到路由树中,后面ServeHTTP触发时,会在路由树中查找对应的回调Handler函数

代码语言:txt
复制
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)
}

最终路由匹配树大概结构如下:

路由树
路由树

启动监听:Run()

初始化工作(日志、错误处理、中间件、路由等)完成后,调用Run()开启http监听

代码语言:txt
复制
func (engine *Engine) Run(addr ...string) (err error) {
	...
	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine) // 底层就是调用http库
	return
}

http请求回调:ServeHTTP()

Gin底层处理就是调用了http库的ListenAndServe,可推理Engine实现了http的回调interface

代码语言:txt
复制
// 1、golang内置net/http库接口定义
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
代码语言:txt
复制
// 2、Engine的ServeHTTP实现
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context) // 从Pool内获取Context实例
	c.writermem.reset(w)              // 初始化http应答包
	c.Request = req                   // 初始化http请求包
	c.reset()                         // 初始化配置等
	engine.handleHTTPRequest(c)       // @处理http请求逻辑
	engine.pool.Put(c)                // 回收Context
}
代码语言:txt
复制
// 3、处理http请求:通过路由树匹配到Handler链并执行
func (engine *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	path := c.Request.URL.Path
	... 
	// 通过路由树计算要回调的Handler
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// 获取路由节点Handler
		handlers, params, tsr := root.getValue(path, c.Params, unescape)
		if handlers != nil {
			c.handlers = handlers
			c.Params = params
			c.Next() // @ 开始执行业务Handler链
			c.writermem.WriteHeaderNow()
			return
		}
		... 
	}
}

源码文件:/context.go

数据结构:type Context struct

从Handler的定义type HandlerFunc func(*Context)可以看到,Context是每个http请求回调函数的唯一入参

代码语言:txt
复制
type Context struct {
	Request   *http.Request  // 请求包
	writermem responseWriter // 响应包

	Params   Params        // 请求Url路径
	handlers HandlersChain // @ 这个请求注册的业务Handler执行链(中间件)
	index    int8          // @ 当前HandlersChain的执行索引
	engine *Engine
	Keys map[string]interface{}	// 业务保存上下文数据
	...
}

中间件执行流:Next()、Abort()

Gin通过index来控制HandlersChain的执行流,正常请求都是index+1从左到右执行,也可调用Next()来实现before和after拦截器能力。如果遇到鉴权失败等情况,可调用Abort()停止后面的handlers执行

代码语言:txt
复制
func (c *Context) Next() {
	c.index++
	// 按index顺序从左到右执行
	for s := int8(len(c.handlers)); c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}
代码语言:txt
复制
func (c *Context) Abort() {
	c.index = abortIndex
}

参数获取:Param()、Query()、PostForm()、Bind()

Gin封装了很多便捷的http参数获取函数,如Query()的源码实现:

代码语言:txt
复制
func (c *Context) GetQueryArray(key string) ([]string, bool) {
	if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 {
		return values, true
	}
	return []string{}, false
}

也可使用Bind()直接转化成业务数据结构,非常好用,Bind的相关实现单独放在/binding目录内,这里就不展开,看.go文件基本也了解了

代码语言:txt
复制
json.go
query.go
xml.go
binding.go
form.go
msgpack.go
uri.go
yaml.go
form_mapping.go
protobuf.go

上下文操作:Get()、Set()

Context提供了数据结构Keys mapstringinterface{}来给业务保存请求的一些上下文信息,业务可通过Set/Get操作

代码语言:txt
复制
func (c *Context) Get(key string) (value interface{}, exists bool) {
	value, exists = c.Keys[key]
	return
}
代码语言:txt
复制
func (c *Context) Set(key string, value interface{}) {
	if c.Keys == nil {
		c.Keys = make(map[string]interface{})
	}
	c.Keys[key] = value
}

源码文件:/routergroup.go

数据结构:type RouterGroup struct

代码语言:txt
复制
type RouterGroup struct {
	Handlers HandlersChain // 关联的Handler执行链
	basePath string
	engine   *Engine
	root     bool
}

路由注册:GET()、POST()、PUT()...

如demo所示,Gin提供了GET、POST等多种http路由注册方法,统一定义在IRoutes接口中:

代码语言:txt
复制
type IRoutes interface {
	Use(...HandlerFunc) IRoutes

	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes

	StaticFile(string, string) IRoutes
	Static(string, string) IRoutes
	StaticFS(string, http.FileSystem) IRoutes
}

路由处理:handle()

虽然Gin提供的路由注册接口很多,但这些接口最后都是调用handle()完成注册处理:

代码语言:txt
复制
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	// 1、计算出绝对路径
	absolutePath := group.calculateAbsolutePath(relativePath) 
	// 2、将handlers加入HandlersChain
	handlers = group.combineHandlers(handlers) 
	// 3、添加到Engine的路由tree
	group.engine.addRoute(httpMethod, absolutePath, handlers) 中
	return group.returnObj()
}

注册中间件:Use()、combineHandlers()

Gin的中间件便于扩展且功能强大,但其实注册执行逻辑还是比较简单,就是对HandlersChain的数组操作

代码语言:txt
复制
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	// 添加到HandlersChain中
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}
代码语言:txt
复制
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	// 判断Hander阈值
	finalSize := len(group.Handlers) + len(handlers)
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	// 就是Append入group.Handlers而已
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
    • Gin框架简介
      • 从示例demo开始
        • 源码文件:/gin.go
          • 源码文件:/context.go
            • 源码文件:/routergroup.go
            • Gin框架简介
              • 最热门的6个Golang框架
                • What is Gin?
                  • 文档资料
                    • 功能特性
                    • 从示例demo开始
                    • 源码文件:/gin.go
                      • 数据结构:type Engine struct
                        • 初始化Engine:New()、Default()
                          • 中间件定义:HandlersChain
                            • 添加路由匹配树:addRoute()
                              • 启动监听:Run()
                                • http请求回调:ServeHTTP()
                                • 源码文件:/context.go
                                  • 数据结构:type Context struct
                                    • 中间件执行流:Next()、Abort()
                                      • 参数获取:Param()、Query()、PostForm()、Bind()
                                        • 上下文操作:Get()、Set()
                                        • 源码文件:/routergroup.go
                                          • 数据结构:type RouterGroup struct
                                            • 路由注册:GET()、POST()、PUT()...
                                              • 路由处理:handle()
                                                • 注册中间件:Use()、combineHandlers()
                                                相关产品与服务
                                                消息队列 TDMQ
                                                消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                                                领券
                                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                                                http://www.vxiaotou.com