Go 源码之 gin 框架

Go 源码之 gin 框架

go源码之gin - Jxy 博客

一、总结

  • gin.New()初始化一个实例:gin.engine,该实例实现了http.Handler接口。实现了ServeHTTP方法

  • 注册路由、注册中间件,调用addRoute将路由和中间件注册到 methodTree 前缀树(节省空间,搜索快)下,methodTree 的非叶子节点是相同 method 的 url 的最长公共前缀字符串,叶子节点是完整的 url 路径

  • http请求会执行ServeHTTP方法,内部会根据请求的 method 和 url 到前缀树 methodTree 中匹配 url,然后遍历 handlers 数组,依次执行c.next()执行,可以通过c.Abort()进行中断

  • 流程:

    • 启动:

      初始化engine;初始化一个长度为 9 的 methodTrees 数组;调用 addRoute 注册全局中间件、路由到 methodTree 数组中

    • 处理:

      gin.engine 实现了 http.handler 接口,实现了 ServeHTTP(ResponseWriter, *Request),所有的请求都会经过 ServeHTTP 处理;

      ServeHTTP 方法:从 methodTrees 中找到本次请求的 httpMethod 的 Tree–>根据请求Url Path 找到Tree下对应的节点nodeValue(包含了中间件handler)—> 执行c.Next() 依次执行handler,,handler内部 可以调用c.JSON等往响应的http写入数据

  • 注意点:

    • 路由中间件的执行顺序和添加顺序一致,遵循先进先出规则
    • c.JSON()等是http请求的末端函数,如果要添加后置拦截器,需要在此之前执行c.Next()即可, c.Abort()为终止执行后续的中间件
  • 使用 sync.Pool 来复用上下午对象

  • gin 底层依旧是依赖 net/http 包,本质上是一个路由处理器,实现了 http.handler 接口,实现了 ServeHTTP

  • 维护了 method 数组,每个元素是一个 radix 树(压缩前缀树)

  • gin 的中间件是使用切片实现的,添加中间件也就是切片追加元素的过程,中间件按追加先后顺序依次执行

  • gin 允许为不同的路由组添加不同的中间件

  • 路由组本质上是一个模版,维护了路径前缀、中间件等信息,让用户省去重复配置相同前缀和中间件的操作

  • 新路由组继承父路由组的所有处理器

  • 如果上下文需要并发处理使用,需要使用上下文副本copy

file

二、源码

(一)engine结构

// Engine的实例,包括了路由组,路由树,中间件和其他等一系列配置
type Engine struct {// 路由组RouterGroup// 如果当前路径无法匹配,但存在带有(不带有)尾斜杠的路径处理程序,RedirectTrailingFlash将启用自动重定向// 例如,如果请求了/foo/,但只有/foo的路由存在,则客户端将被重定向到/foo// GET请求的http状态代码为301,所有其他请求方法的http状态为307。RedirectTrailingSlash bool// RedirectFixedPath如果启用,如果没有为其注册句柄,则路由器将尝试 修复 当前请求路径。// 首先删除像../或//这样的多余路径元素。然后,路由器对清理后的路径进行不区分大小写的查找。如果可以找到该路由的句柄,则路由器对GET请求使用状态代码301,// 对所有其他请求方法使用状态代码307,重新定向到正确的路径。例如/FOO和/..//FOO可以重定向到/FOO。// RedirectTrailingFlash独立于此选项。RedirectFixedPath bool// HandleMethodNotAllowed如果启用,则如果当前请求无法路由,则路由器会检查当前路由是否允许其他方法。// 如果是这种情况,则使用“Method Not Allowed”和 HTTP状态代码405 回答请求。// 如果不允许其他方法,则将请求委托给NotFound处理程序。HandleMethodNotAllowed bool// ForwardedByClientIP(如果启用),将从与存储在“(*gin.Engine).RemoteIPHeaders”中的标头匹配的请求标头解析客户端IP。// 如果未提取任何IP,它将返回到从“(*gin.Context).Request.Remoddr”获取的IP。ForwardedByClientIP bool// AppEngine was deprecated.// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD// #726 #755 If enabled, it will trust some headers starting with// 'X-AppEngine...' for better integration with that PaaS.AppEngine bool// UseRawPath if enabled, the url.RawPath will be used to find parameters.// UseRawPath(如果启用),url.RawPath将用于查找参数。UseRawPath bool// UnescapePathValues如果为true,则路径值将被取消转义。// 如果UseRawPath为false(默认情况下),则UnescapePathValues实际上为true,// 作为url。将使用路径,该路径已未被覆盖。UnescapePathValues bool// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.// See the PR #1817 and issue #1644RemoveExtraSlash bool// RemoteIPHeaders list of headers used to obtain the client IP when// `(*gin.Engine).ForwardedByClientIP` is `true` and// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.RemoteIPHeaders []string// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by// that platform, for example to determine the client IPTrustedPlatform string// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm// method call.MaxMultipartMemory int64// UseH2C enable h2c support.UseH2C bool// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.ContextWithFallback booldelims           render.Delims// https://cloud.tencent.com/developer/article/1580456secureJSONPrefix string  HTMLRender       render.HTMLRender// FuncMap是定义从名称到函数的映射的映射类型。// 每个函数必须有一个返回值或两个返回值,其中第二个返回值具有类型错误。// 在这种情况下,如果在执行期间第二个(error)参数的计算结果为非nil,则执行终止,Execute返回该错误。// FuncMap在“text/template”中具有与FuncMap相同的基本类型,复制到此处,因此客户端无需导入“text/template”。FuncMap          template.FuncMap trees            methodTrees // 请求的method数组,每个元素是一个前缀树}

(二)ServeHTTP(核心处理http方法)

// 所有的http请求最终都会走这里
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 从池里取一个contextc.writermem.reset(w) // 重置c.Request = req // 赋值请求c.reset() // 重置数据engine.handleHTTPRequest(c) // 核心处理函数engine.pool.Put(c) // 放回缓冲池
}
// 核心处理函数
func (engine *Engine) handleHTTPRequest(c *Context) {// 请求的Method类型,如GET等httpMethod := c.Request.Method// 请求的路径rPath := c.Request.URL.Pathunescape := falseif engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues}if engine.RemoveExtraSlash {rPath = cleanPath(rPath)}// 从methodTree中匹配method和请求urlt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// 从前缀树methodTree中匹配到叶子节点valuevalue := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}// 执行中间件if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next() // 依次从handlers数组中FIFO执行中间件c.writermem.WriteHeaderNow() // 最终写入http响应流responseWriter中return}if httpMethod != http.MethodConnect && rPath != "/" {if value.tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)return}if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}}break}if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method == httpMethod {continue}if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}}c.handlers = engine.allNoRouteserveError(c, http.StatusNotFound, default404Body)
}

(三)methodTrees

在New() 中会初始化容量为9,匹配9个http.Method

每个节点都是压缩前缀树:将相同请求method的url计算出最长公共前缀字符串然后作为子节点

type methodTrees []methodTree// 压缩前缀树,存储了http.Method的请求路径
type methodTree struct {method stringroot   *node
}
type node struct {path      string // 存储:共同的最长前缀字符indices   stringwildChild boolnType     nodeTypepriority  uint32children  []*node // 有共同的最长前缀字符path的url pathhandlers  HandlersChainfullPath  string  // 叶子节点存储的是完整的请求路径
}
// 构建树的函数
func addRoute(){}
r := gin.Default()r.Use(gin.Recovery(), gin.Logger())r.GET("/user/GetUserInfo", func(context *gin.Context) {})r.GET("/user/GetManyUserInfo", func(context *gin.Context) {})r.Run(":9091")

file

(四)context结构

// gin.Context是gin框架中最重要的一部分
type Context struct {writermem responseWriter // 响应的数据流Request   *http.Request //  请求句柄Writer    ResponseWriter // 响应的Writerhandlers HandlersChain // 中间件handler数组index    int8 // handler数组的下标,表示已经执行的下标fullPath string // 完整的url路径engine       *Engine
}

(五)RouterGroup

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
// RouterGroup在内部用于配置路由器,RouterGroup与前缀和处理程序(中间件)数组相关联。
type RouterGroup struct {Handlers HandlersChain  // 中间件handlerbasePath string // 基础路径engine   *Engine root     bool // 是否是根节点
}

file

file

file

(六)c.GET()

// 调用了handler,httpMethod=http.MethodGet,其他什么c.POST等都是差不多
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)// 将请求的handler和group的全局handler合并handlers = group.combineHandlers(handlers)// 添加路由到前缀树methodTree中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
// 根据请求的method和path,构建前缀树methodTree
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {assert1(path[0] == '/', "path must begin with '/'")assert1(method != "", "HTTP method can not be empty")assert1(len(handlers) > 0, "there must be at least one handler")debugPrintRoute(method, path, handlers)// 从数组methodTrees中获取对应method的根节点root := engine.trees.get(method)if root == nil {// 不存在,常见根节点root = new(node)root.fullPath = "/"engine.trees = append(engine.trees, methodTree{method: method, root: root})}// 构建前缀树root.addRoute(path, handlers)// Update maxParamsif paramsCount := countParams(path); paramsCount > engine.maxParams {engine.maxParams = paramsCount}if sectionsCount := countSections(path); sectionsCount > engine.maxSections {engine.maxSections = sectionsCount}
}

(七)c.JSON

func (c *Context) JSON(code int, obj any) {c.Render(code, render.JSON{Data: obj})
}
// 将数据写入c.Writer,但是还没有响应http
func (c *Context) Render(code int, r render.Render) {c.Status(code)if !bodyAllowedForStatus(code) {r.WriteContentType(c.Writer)c.Writer.WriteHeaderNow()return}if err := r.Render(c.Writer); err != nil {panic(err)}
}

(八)c.Next()

非常巧妙的设计,这个设计可以用来暂停执行当前handler,先执行后面的handler,然后再执行当前handler后面的代码

// handlers是一个中间件执行函数的数组,[]HandlerFunc
func (c *Context) Next() {// index记录 已经执行到数组[]HandlerFunc的下标,// index++ 继续执行后面的handlerFuncc.index++ for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++}
}

(九)c.Abort()

可以用来终止后面所有handler的执行,

// 这里将 c.index的值改了超级大,在c.Next()中会判断c.index<len(handler),从而达到终止handler执行的效果
func (c *Context) IsAborted() bool {return c.index >= abortIndex
}

三、常见问题

如何设置前置拦截器和后置拦截器

  • 方法一:

    利用 handler 的存储结构:所有的 handler 会按顺序添加到数数组 []HandlerFunc 中,执行的是按FIFO遍历执行,所有先添加的handler会先执行,也就是说越先添加的就是前置拦截器,越晚添加的就是后置拦截器

    	r := gin.Default()r.Use(gin.Recovery()) // 前置拦截器r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中间执行函数r.Use(func(context *gin.Context) {}) // 后置拦截器
    
  • 方法二

    使用c.Next()方法,在handler函数内部,可以先执行一部分代码,然后执行c.Next(),会遍历执行后续的handler,当所有的handler结束后,在执行当前handler c.Next()之后的代码

    	r := gin.Default()r.Use(, func(c *gin.Context) {// 前置代码c.Next() // 执行所有handler// 后置代码}) r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中间执行函数
有劳各位看官 点赞、关注➕收藏 ,你们的支持是我最大的动力!!!
接下来会不断更新 golang 的一些底层源码及个人开发经验(个人见解)!!!
同时也欢迎大家在评论区提问、分享您的经验和见解!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/784648.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HashSet解析

文章目录 集合简介对HashSet进行遍历迭代器增强forLambda表达式 Hash底层原理 集合简介 HashSet是Set集合下的子接口&#xff0c;set集合添加的元素是无索引&#xff0c;不重复&#xff0c;无序&#xff0c;与List系列集合正好相反。 无序&#xff1a;存储顺序不一致。 不重复…

全新的分布式锁,几行代码搞定,简单且强大

# 前言 分布式锁是分布式系统中一个极为重要的工具。目前有多种分布式锁的设计方案&#xff0c;比如借助 redis&#xff0c;mq&#xff0c;数据库&#xff0c;zookeeper 等第三方服务系统来设计分布式锁。tldb 提供的分布式锁&#xff0c;主要是要简化这个设计的过程&#xff0…

数据挖掘入门项目二手交易车价格预测之特征工程

文章目录 目标常见的特征工程具体步骤1. 导入数据2. 删除异常值3. 特征构造3.1 为树模型构造特征3.2 为LR NN 之类的模型构造特征 4. 特征筛选过滤式包裹式嵌入式 5. 总结 本文数据集来自阿里天池&#xff1a;https://tianchi.aliyun.com/competition/entrance/231784/informat…

初始Java篇(JavaSE基础语法)(5)(类和对象(下))

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 封装 访问限定符 封装扩展之包 自定义包 static成员 static修饰成员变量 static修饰成员方法 static成员变量初始化 内部类 对象的打…

Adaboost集成学习 | Matlab实现基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | 基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)基于SVM(支持向量机)和AdaBoost集成学习的时间序列预测(如股票价格预测)是一种结合了两种强大机器学习算…

《极客时间TonyBai go语言第一课》学习笔记

文章目录 前置篇显式组合并发 入门篇大纲 前置篇 显式 在 C 语言中&#xff0c;下面这段代码可以正常编译并输出正确结果&#xff1a; #include <stdio.h> int main() { short int a 5; int b 8; long c 0; c a b; printf("%ld\n", c); }我们看到在上面…

Windows 电脑麦克风 自动启用/禁用 小玩具!

WinMicrophone Windows 系统的 麦克风设备&#xff08;启用/禁用&#xff09;切换驱动&#xff01;它是小巧且快速的&#xff0c;它能够自动的检测并切换麦克风的情况。 您可以在软件包仓库中找到发布版本的exe包&#xff0c;无需安装&#xff01;其能够大大增大您在Windows中…

【网站项目】面向社区健康服务的医疗平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【MATLAB源码-第25期】基于matlab的8QAM调制解调仿真,手动实现未调用内置函数,星座图展示。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 8QAM调制&#xff08;8 Quadrature Amplitude Modulation&#xff09;是一种数字调制技术&#xff0c;它可以在有限带宽内传输更多的信息比特。在8QAM调制中&#xff0c;每个符号可以携带3个比特的信息。QAM调制是将数字信号…

插入排序---算法

1、算法概念 插入排序&#xff1a;它的工作原理是通过构建有序排序&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置插入。 2、算法步骤 将第一待排序序列第一个元素看作一个有序序列&#xff0c;把第二个元素到最后一个元素当成是…

内容营销新手指南:Kompas.ai的智能起步策略

内容营销是当今企业获取客户、建立品牌认知和忠诚度的关键手段。然而&#xff0c;对于刚刚踏入这一领域的新手来说&#xff0c;内容营销的世界可能会显得既广阔又复杂。从内容创作到发布&#xff0c;再到分析和优化&#xff0c;每一步都充满了挑战。本文旨在为内容营销新手提供…

数据资产如何入表?有哪些步骤?

当下&#xff0c;数据资产入表这一关键环节对于企业的高效运营与决策制定具有至关重要的作用。软信天成团队经过长期研究与实践&#xff0c;将数据资产入表的全流程归纳总结为四个核心步骤&#xff0c;以期为诸位提供帮忙。 &#xff08;一&#xff09;确保数据合规&#xff0…

jvm(HotSpotVM)学习记录

1&#xff1a;当我们写的java文件经过编译器编译后生成class文件。class文件里面包含了各种java语言规范的特定语法。可以通过javap -v -private xxx.class 例如&#xff1a; javap -v -private Gas.class Classfile /D:/project/ike-springboot-carbonzero-v2/springboot-bms…

前端三剑客 —— CSS (第二节)

目录 内容回顾&#xff1a; CSS选择器*** 属性选择器 伪类选择器 1&#xff09;:link 超链接点击之前 2&#xff09;:visited 超链接点击之后 3&#xff09;:hover 鼠标悬停在某个标签上时 4&#xff09;:active 鼠标点击某个标签时&#xff0c;但没有松开 5&#xff09;:fo…

LabVIEW动车组谐波分析与检测系统

LabVIEW动车组谐波分析与检测系统 随着中国高速铁路网络的快速发展&#xff0c;动车组数量和运行速度的不断提升&#xff0c;其产生的谐波问题对电网产生了不小的影响。基于图形化编程语言LabVIEW&#xff0c;开发了一套动车组谐波分析与检测系统&#xff0c;旨在实时监控与分…

机器视觉/将HIK海康面阵相机连接Halcon软件

文章目录 概述工业相机客户端动态库拷贝Halcon连接HIK相机的配置相机参数其他 概述 本文简述了如何将海康面阵相机连接到Halcon软件中进行实时取图的过程。 补充&#xff0c; 整个实践过程使用 17.12 / x64-win64 Halcon 软件版本 海康 MV-CE200-10GM 面阵相机。从左到右简解…

B树、B+树、哈夫曼树

目录 1. B树2. B树3. 哈夫曼树 1. B树 特点&#xff1a;一个节点当中可以有多个值&#xff0c;节点内部key 值是有序的&#xff0c;节点内部存储的是key-value类型的数据 磁盘中文件存储用B树。 4阶B树一个节点最多三个key值 5阶B树一个节点最多四个key值 B树有很多的分支&…

华为OD机试 - 绘图机器 - 双指针(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

Matlab中的脚本和函数

Matlab中的脚本和函数 文章目录 Matlab中的脚本和函数脚本创建脚本代码注释函数创建函数局部函数嵌套函数私有函数匿名函数补充知识函数句柄测试环境:Win11 + Matlab R2021a 脚本 ​ Matlab脚本是最简单的程序文件类型。它们可用于自动执行一系列 Matlab 命令,如命令行重复执…

ROS2 学习(一)ROS2 简介与基本使用

参考引用 动手学 ROS2 1. ROS2 介绍与安装 1.1 ROS2 的历史 ROS&#xff08;Robot Operating System&#xff0c;机器人操作系统&#xff09;&#xff0c;但 ROS 本身并不是一个操作系统&#xff0c;而是可以安装在现在已有的操作系统上&#xff08;Linux、Windows、Mac&…