博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang之Gin框架源码解读——第二章
阅读量:3898 次
发布时间:2019-05-23

本文共 4053 字,大约阅读时间需要 13 分钟。

Gin是使用Go语言编写的高性能的web服务框架,根据官方的测试,性能是httprouter的40倍左右。要使用好这套框架呢,首先我们就得对这个框架的基本结构有所了解,所以我将从以下几个方面来对Gin的源码进行解读。

  • :Gin是如何储存和映射URL路径到相应的处理函数的
  • :Gin中间件的设计思想及其实现
  • :Gin是如何解析客户端发送请求中的参数的
  • :Gin是如何将各类格式(JSON/XML/YAML等)数据解析返回的

Gin中间件的设计思想及其实现

在第一章中我们谈到Gin的中间件是基于RouterGroup数据结构实现的,这里我们再来回顾一下这个数据结构:

type HandlerFunc func(*Context)type HandlersChain []HandlerFunctype RouterGroup struct {
//中间件处理链 Handlers HandlersChain //当前的路由基地址 basePath string //Gin框架的核心引擎 engine *Engine //当前 root bool}

下面,我们以官方的示例代码来逐步研究其运行机制

func main() {
//创建一个不包含任何中间件的Engine r := gin.New() //添加日志中间件 r.Use(gin.Logger()) //添加错误回复重定向中间件 r.Use(gin.Recovery()) //以上这三部可以使用r := gin.Default()一步实现 //对/benchmark路由添加两个处理函数 r.GET("/benchmark", MyBenchLogger(), benchEndpoint) //创建路由分组 authorized := r.Group("/") //使用AuthRequired中间件 authorized.Use(AuthRequired()) //这里的括号只是为了看起来整齐 {
//这里就是简单的路由信息配置 authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint) testing := authorized.Group("testing") //对/testing/analytics添加路由信息 testing.GET("/analytics", analyticsEndpoint) } r.Run(":8080")}func AuthRequired() gin.HandlerFunc {
return gin.BasicAuth(gin.Accounts{
"foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", });}

Endpoint结尾的均是HandlerFunc类型的处理函数

首先,我们来看添加中间件的函数

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...) return group.returnObj()}

从这里我们可以看出Use函数不过就是将我们添加的中间件函数存储到了RouterGroupHandlers中,然后在添加搜索树节点时,会与我们的路由处理函数合并,这个我们在上一章中也提到过,就像下面这样。

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)}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()}

在树的节点中就像下图这样:

在这里插入图片描述

中间件实现方式

基本上每个路由的最终匹配节点都会同时含有中间件处理函数和路由处理函数,就像上图其中的 ( 0 ) 、 ( 1 ) (0)、(1) (0)(1)是我们添加的中间件函数,而 ( 2 ) (2) (2)则是我们为该路由添加的处理函数。所以每当请求被路由到某个节点时,都会遍历执行其下的handler处理函数链。

设计思想

由此可见,Gin框架的中间件采用的是责任链模式,而不是常见的代理模式(Spring为代表)来实现的。

路由分组

除了上面的普通Use函数来添加中间件外,Gin还支持为某一指定路径添加一组路由中间件,使用如下函数实现:

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
//合并已存在的处理函数 Handlers: group.combineHandlers(handlers), //计算与当前路由分组的相对路径,默认有个根路由分组,基地址为"/" basePath: group.calculateAbsolutePath(relativePath), engine: group.engine, }}

通过创建一个路由分组,即可实现为特定路由前缀的节点应用中间件函数,而不是对整个根树所有终节点应用。例如:

//创建一个以"/"开头的路由分组authorized := r.Group("/")//对所有该分组下的路由应用AuthRequired中间件authorized.Use(AuthRequired()){
//为"/login"路由添加处理函数"loginEndpoint" // 同时也会被应用AuthRequired中间件 authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint) //以authorized为基础,创建一个子分组, // 即匹配"/testing"开头的路由 testing := authorized.Group("testing") //为"/testing/analytics"路由添加处理函数analyticsEndpoint //同时也会被应用AuthRequired中间件 testing.GET("/analytics", analyticsEndpoint)}func AuthRequired() gin.HandlerFunc {
return gin.BasicAuth(gin.Accounts{
"foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", });}

注意事项

在研读Gin源码时,我发现如下代码:

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) {
panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers}

从上面的代码,可以看出每个节点的处理函数是有上限的,最多为abortIndex(63)个,超出后就会抛出错误。特别要注意的是这里限制的个数是路由分组中所有中间件的个数+我们自己编写的处理函数的总个数,所以在对业务进行模块化时可能会抽离出很多中间件,这个时候一定要注意切不可分割得太细,否则可能会出现上述问题。

转载地址:http://iqfen.baihongyu.com/

你可能感兴趣的文章
ejarmaker: jar 、java类的加密工具
查看>>
配置NFS实现Linux服务器之间的文件共享
查看>>
PostgreSQL连接池pgbouncer的使用
查看>>
Kryo序列化进阶学习: 加密数据
查看>>
swift 3.0 数组赋值
查看>>
用C#通过888-TT打印中文标签
查看>>
sendmail 出现 My unqualified host name的解决办法
查看>>
彻底解决lazarus安装组件后烦人的编译时单元找不到的问题!
查看>>
Delphi的参数修饰const/var/output 与C++的对应关系
查看>>
C++ free与delete区别
查看>>
VC的字符串转换atlconv的使用
查看>>
Twitter的分布式自增ID算法snowflake (Java版)
查看>>
CentOS7 安装配置FastDFS
查看>>
递归算法的时间复杂度
查看>>
数据结构之图(存储结构、遍历)
查看>>
使用sizeof计算类的大小
查看>>
乐观锁与悲观锁——解决并发问题
查看>>
operator 类型转换及重载
查看>>
HTTP状态码
查看>>
TCP/IP详解--举例明白发送/接收缓冲区、滑动窗口协议之间的关系
查看>>