gin 源码浅析 1)

842 阅读4分钟

1. gin 简介

Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

gin 是使用 golang 编写的高性能 web 开发框架

2. gin 框架重要对象

  • Context : 单次 web 请求的上下文对象,用于存储请求及响应等相关数据。gin 从接收请求起开始构造 Context,然后交由与请求对应的函数链依次进行处理并生成最后的响应

  • IRouter : 一系列管理和注册路由函数的方法集合(接口),如 Use/GET/POST/Group 等方法。EngineRouterGroup 都实现了 IRouter 接口

  • Engine : web 服务的总引擎,提供服务配置、注册 middleware、注册路由函数、启动服务等方法,同时也可以看做是前缀为空字符串的根 RouterGroup

  • RouterGroup : 方便开发者对 URL 进行前缀分组管理的对象,它关联了 URL 前缀和与之绑定的函数链。对其方法的调用本质上还是回归到对 Engine 对象方法的调用

  • HandlersChain : 以 func(*gin.Context) 为原型的函数链,既可以用来表示路由函数,也可以用来表示 middleware

  • HandlerFunc : 即 func(*gin.Context),既可以表示路由函数,也可以表示 middleware,一般情况下一个 URL 匹配一个路由函数(也可以是多个 middleware 加一个路由函数的组合),并且路由函数位于函数链的最后一个,而在路由函数之前的 HandlerFunc 一般都属于 middleware,可用于进行参数校验、异常处理、日志打印、权限认证等用途。middleware 处理完请求后需要调用且只能调用一次 context.Next() 方法以驱动函数链的链式执行

  • methodTrees : 请求方法树,无论是注册路由还是注册中间件,其本质就是在对应的 methodTree 中添加新节点。不同的请求方法将对应一棵 methodTree,每棵 methodTree 可以看做是一个 radix tree(radix tree 类似于字典树,可支持路径模式匹配)。当进行路由匹配时,gin 将通过对应的请求方法和请求路径去找到对应的 methodTree,然后遍历 methodTree 找到与请求路径匹配的唯一节点 node,并执行与此节点绑定的 HandlersChain

    为什么匹配路由不使用哈希表呢?对于 web 框架而言,注册路由函数的路径一般需要支持动态变量或者通配符的,例如 /books/:book_id 等,也就是说注册路由函数的路径是一种 pattern,而哈希表无法匹配 pattern,对于不同的路径将映射到唯一的哈希值

3. gin 框架关键方法

a). gin.New

返回不使用任何 middlewareEngine 实例

与之类似的方法是 gin.Default() ,默认注册了两个 middleware 

b). RouterGroup.Use

将待注册的 middleware 添加到 RouterGroup 的函数链中 

Engine 也有同样的方法,本质是为当前 Engine 实例的 RouterGroup 属性注册 middleware

c). RouterGroup.GET

只要是注册路由函数,无论是 GET/POST/PUT/PATCH/HEAD/OPTIONS/DELETE/CONNECT/TRACE/ANY 等方法,最终还是调用到 RouterGroup.Handle(httpMethod, relativePath string, handlers HandlersChain)

Handle 方法在对 handles 进行扩充后,将调用 engine.addRoute(method, path string, handlers HandlersChain)EnginemethodTrees 新增节点

d). engine.ServeHttp

gin 框架是对 golanghttp 包做了封装,在执行 Run 函数的时候就开始监听某个端口的请求了,

而对于 http.ListenAndServe 的第二个参数,它需要实现 ServeHttp 接口

Engine 的实现中,首先构造一个全新的 *gin.Context ,然后再将这个 Context 实例传入到对应的路由函数链进行处理

值得一提的是这里使用了协程安全的 sync.Pool 进行 Context 对象的管理,假设请求的 qps 越高,那么 Context 对象的创建也就越频繁,这意味着 gc 的频率也就越高,为了避免重复的对象创建和销毁,sync.Pool 将不使用的对象进行了缓存,等到下次需要使用的时候只需要调用 pool.Get 方法拿到缓存的对象,并且清空对象,就可以重复使用了,这减轻了 golang 垃圾回收的压力

通过请求路径及参数匹配请求方法树的节点,拿到相应节点的函数链,然后调用 context.Next() 驱动函数链的执行

4. 总结

  1. Engine 对象是配置或启动 web 服务的入口

  2. RouterGroup 对象是方便开发者对 URL 进行分组管理而设计的,对它的操作最终都会转换为对 Engine 的操作

  3. Engine 维护了请求方法树集合 methodTrees,每注册一次 URL 就会在对应的请求方法树中新增节点,每次匹配请求时都需要遍历一次请求方法树以得到请求的目标节点

  4. Context 贯穿了请求的接受和响应,gin 框架使用它在目标节点的函数链之间传递,也利用它进行函数链的驱动执行

定期推送与后端程序相关的博文