gin框架剖析(一)(gin框架实战)
ccwgpt 2024-09-18 12:19 39 浏览 0 评论
gin 是目前 Go 里面使用最广泛的框架之一了,弄清楚 gin 框架的原理,有助于我们更好的使用 gin。这个系列 gin 源码阅读会逐步讲明白 gin 的原理,欢迎关注后续文章。
gin 概览
想弄清楚 gin, 需要弄明白以下几个问题:
- request数据是如何流转的。
- gin框架到底扮演了什么角色。
- 请求从gin流入net/http, 最后又是如何回到gin中。
- gin的context为何能承担起来复杂的需求。
- gin的路由算法。
- gin的中间件是什么。
- gin的Engine具体是个什么东西。
- net/http的requeset, response都提供了哪些有用的东西。
从gin的官方第一个demo入手:
r.Run() 的源码:
看到开始调用的是 http.ListenAndServe(address, engine), 这个函数是net/http的函数, 然后请求数据就在net/http开始流转。
Request 数据是如何流转的
先不使用gin, 直接使用net/http来处理http请求:
在浏览器中输入localhost:8000, 会看到Hello World. 下面利用这个简单demo看下request的流转流程。
HTTP是如何建立起来的
简单的说一下http请求是如何建立起来的:(需要有基本的网络基础, 可以找相关的书籍查看, 推荐看UNIX网络编程卷1:套接字联网API)
在TCP/IP五层模型下, HTTP位于应用层, 需要有传输层来承载HTTP协议. 传输层比较常见的协议是TCP,UDP, SCTP等. 由于UDP不可靠, SCTP有自己特殊的运用场景, 所以一般情况下HTTP是由TCP协议承载的(可以使用wireshark抓包然后查看各层协议)。
使用TCP协议的话, 就会涉及到TCP是如何建立起来的. 面试中能够常遇到的名词三次握手, 四次挥手就是在这里产生的. 具体的建立流程就不在陈述了, 大概流程就是图中左半边。
所以说, 要想能够对客户端http请求进行回应的话, 就首先需要建立起来TCP连接, 也就是socket. 下面要看下net/http是如何建立起来socket?
net/http 是如何建立 socket 的
从图上可以看出, 不管server代码如何封装, 都离不开bind,listen,accept这些函数. 就从上面这个简单的demo入手查看源码。
注册路由
这段代码是在注册一个路由及这个路由的handler到DefaultServeMux中。
可以看到这个路由注册太过简单了, 也就给gin, iris, echo等框架留下了扩展的空间, 后面详细说这个东西。
服务监听及响应
上面路由已经注册到net/http了, 下面就该如何建立socket了, 以及最后又如何取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端。
1.创建 socket
2.Accept 等待客户端链接
3.提供回调接口 ServeHTTP
4.回调到实际要执行的 ServeHTTP
这基本是整个过程的代码。
ln, err := net.Listen("tcp", addr)做了初试化了socket, bind, listen的操作。
rw, e := l.Accept()进行accept, 等待客户端进行连接。
go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作。
h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端。
从这里也能够看出来, net/http基本上提供了全套的服务。
为什么会出现很多go框架
从这段函数可以看出来, 匹配规则过于简单, 当能匹配到路由的时候就返回其对应的handler, 当不能匹配到时就返回/. net/http的路由匹配根本就不符合 RESTful 的规则,遇到稍微复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。
所以基本所有的go框架干的最主要的一件事情就是重写net/http的route。我们直接说 gin就是一个 httprouter 也不过分, 当然gin也提供了其他比较主要的功能, 后面会一一介绍。
综述, net/http基本已经提供http服务的70%的功能, 那些号称贼快的go框架, 基本上都是提供一些功能, 让我们能够更好的处理客户端发来的请求. 如果你有兴趣的话,也可以基于 net/http 做一个 Go 框架出来。
这个例子中 http.HandleFunc 通过看源码,可以看到 URI "/" 被注册到了 DefaultServeMux 上。
net/http ServeHTTP 的作用
net/http 里面有个非常重要的 Handler interface。只有实现了这个方法才能请求的处理逻辑引入自己的处理流程中。
默认的 DefaultServeMux 就实现了这个 ServeHTTP。
这个 request 的流转过程:
socket.accept 接收到客户端请求后,启动 go c.serve(connCtx) [net/http server.go:L3013]行,专门处理这次请求,server 继续等待客户端连接。
获取能处理这次请求的 handler -> serverHandler{c.server}.ServeHTTP(w, w.req) [net/http server.go:L1952]。
跳转到真正的 ServeHTTP 去匹配路由,获取 handler。
由于并没有自定义路由,于是使用的是 net/http 默认路由 [net/http server.go:L2880-2887]。
所以最终调用去 DefaultServeMux 匹配路由,输出返回对应的结果。
探究 gin ServeHTTP 的调用链路
下面是 gin 的官方 demo, 仅仅几行代码,就启动了一个 echo server:
这段代码的大概流程:
r := gin.Default() 初始化了相关的参数。
将路由 /ping 以及对应的 handler 注册到路由树中。
使用 r.Run() 启动 server。
r.Run 的底层依然是 http.ListenAndServe。
所以 gin 建立 socket 的过程,accept 客户端请求的过程与 net/http 没有差别,会同样重复上面的过程。唯一有差别的位置就是在于获取 ServeHTTP 的位置。
由于 sh.srv.Handler 是 interface 类型,但是其真正的类型是 gin.Engine,根据 interace 的动态转发特性,最终会跳转到 gin.Engine.ServeHTTP 函数中。
gin.ServeHTTP 的实现:
至此,终于我们看到了 gin.ServeHTTP 的全貌了。
从 sync.pool 里面拿去一块内存。
对这块内存做初始化工作,防止数据污染。
处理请求 handleHTTPRequest。
请求处理完成后,把这块内存归还到 sync.pool 中。
现在看起来这个实现很简单,其实不然,这才是 gin 能够处理数据的第一步,也仅仅将请求流转入 gin 的处理流程而已。
这里做个结论:通过上面的源码流程分析,我们知道 net/http.ServeHTTP 这个函数相当重要性, 主要有这个函数的存在, 才能将请求流转入目前 Go 的这些框架里面。同学们有兴趣的话,可以去看看 echo, iris, go-zero 等框架是如何实现 ServeHTTP 的。
什么是路由?
这个其实挺容易理解的,就是根据不同的 URL 找到对应的处理函数即可。
目前业界 Server 端 API 接口的设计方式一般是遵循 RESTful 风格的规范。当然我也见过某些大公司为了降低开发人员的心智负担和学习成本,接口完全不区分 GET/POST/DELETE 请求,完全靠接口的命名来表示。
举个简单的例子,如:"删除用户":
这种 No RESTful 的方式,有的时候确实减少一些沟通问题和学习成本,但是只能内部使用了。这种不区分 GET/POST 的 Web 框架一般设计的会比较灵活,但是开发人员水平参差不齐,会导致出现很多“接口毒瘤”,等你发现的时候已经无可奈何了,如下面这些接口:
这样的接口设计会导致开源的框架都是解析不了的,只能自己手动一层一层 decode 字符串,这里就不再详细铺开介绍了,等下一节说到 gin Bind 系列函数时再详细说一下。
继续回到上面 RESTful 风格的接口上面来,拿下面这些简单的请求来说:
这是比较规范的 RESTful API设计,分别代表:
获取 userID 的用户信息。
更新 userID 的用户信息(当然还有其 json body,没有写出来)。
创建 userID 的用户(当然还有其 json body,没有写出来)。
删除 userID 的用户。
可以看到同样的 URI,不同的请求 Method,最终其他代表的要处理的事情也完全不一样。
看到这里你可以思考一下,假如让你来设计这个路由,要满足上面的这些功能,你会如何设计呢?
gin 路由设计
如何设计不同的 Method ?
通过上面的介绍,已经知道 RESTful 是要区分方法的,不同的方法代表意义也完全不一样,gin 是如何实现这个的呢?
其实很简单,不同的方法就是一棵路由树,所以当 gin 注册路由的时候,会根据不同的 Method 分别注册不同的路由树。
如这四个请求,分别会注册四颗路由树出来。
其实代码也很容易看懂:
拿到一个 method 方法时,去 trees slice 中遍历,
如果 trees slice 存在这个 method, 则这个URL对应的 handler 直接添加到找到的路由树上;
如果没有找到,则重新创建一颗新的方法树出来, 然后将 URL对应的 handler 添加到这个路由 树上。
gin 路由的注册过程
这段简单的代码里,r.Get 就注册了一个路由 /ping 进入 GET tree 中。这是最普通的,也是最常用的注册方式。
不过上面这种写法,一般都是用来测试的,正常情况下我们会将 handler 拿到 Controller 层里面去,注册路由放在专门的 route 管理里面,这里就不再详细拓展,等后面具体说下 gin 的架构分层设计。
使用 RouteGroup
RouteGroup 是非常重要的功能,举个例子:一个完整的 server 服务,url 需要分为鉴权接口和非鉴权接口,就可以使用 RouteGroup 来实现。其实最常用的,还是用来区分接口的版本升级。这些操作, 最终都会在反应到gin的路由树上
gin 路由的具体实现
还是从这个简单的例子入手。我们只需要弄清楚下面三个问题即可:
URL->ping 放在哪里了?
handler-> 放在哪里了?
URL 和 handler 是如何关联起来的?
1.GET/POST/DELETE/..的最终归宿
在调用POST, GET, HEAD等路由HTTP相关函数时, 会调用handle函数。handle 是 gin 路由的统一入口。
2. 生成路由树
下面考虑一个情况,假设有下面这样的路由,你会怎么设计这棵路由树?
当然最简单最粗暴的就是每个字符串占用一个树的叶子节点,不过这种设计会带来的问题:占用内存会升高,我们看到 abc, abd, af 都是用共同的前缀的,如果能共用前缀的话,是可以省内存空间的。
gin 路由树是一棵前缀树. 我们前面说过 gin 的每种方法(POST, GET ...)都有自己的一颗树,当然这个是根据你注册路由来的,并不是一上来把每种方式都注册一遍。
gin 每棵路由大概是下面的样子:
这个流程的代码太多,这里就不再贴出具体代码里,有兴趣的同学可以按照这个思路看下去即可。
- handler 与 URL 关联
node 是路由树的整体结构:
children 就是一颗树的叶子结点。每个路由的去掉前缀后,都被分布在这些 children 数组里。
path 就是当前叶子节点的最长的前缀。
handlers 里面存放的就是当前叶子节点对应的路由的处理函数。
当收到客户端请求时,如何找到对应的路由的handler?
《gin 源码阅读(2) - http请求是如何流入gin的?》第二篇说到 net/http 非常重要的函数 ServeHTTP,当 server 收到请求时,必然会走到这个函数里。由于 gin 实现这个 ServeHTTP,所以流量就转入 gin 的逻辑里面。
所以,当 gin 收到客户端的请求时, 第一件事就是去路由树里面去匹配对应的 URL,找到相关的路由, 拿到相关的处理函数。其实这个过程就是 handleHTTPRequest 要干的事情。
从代码上看这个过程其实也很简单:
遍历所有的路由树,找到对应的方法的那棵树,
匹配对应的路由,
找到对应的 handler。
总结:说到这里,基本上把 gin 路由的整个流程说清楚了,本期关于gin就介绍到这,后期会继续更新.
相关推荐
- 5 分钟搭建 Node.js 微服务原型(node 微服务架构)
-
微服务已成为在Node.js中构建可扩展且强大的云应用的主流方法。同时也存在一些门槛,其中一些难点需要你在以下方面做出决策:组织项目结构。将自定义服务连接到第三方服务(数据库,消息代理等)处理微服...
- 当前的前端,真的不配叫程序员吗?
-
今天看到一个比较令人震惊的帖子,说前端不配叫程序员,令我很吃鲸,是谁我就不说了,帖子出处是一个大龄程序员组里面的,想想也不觉得奇怪了,毕竟对于年龄比较大的程序员来说,前端起步比较晚,最开始就是一个切图...
- 聊聊asp.net中Web Api的使用(asp.net core web api教程)
-
扯淡随着app应用的崛起,后端服务开发的也越来越多,除了很多优秀的nodejs框架之外,微软当然也会在这个方面提供更便捷的开发方式。这是微软一贯的作风,如果从开发的便捷性来说的话微软是当之无愧的老大哥...
- NodeJS中,listen Access:permission denied解决办法
-
错误描述:Win10系统,NodeJS程序。使用express框架开发的http服务器,启动时出现错误提示“listenAccess:permissiondenied"。错误原因:这是由于...
- Hono — 下一代高性能web框架(天融信下一代vnp)
-
最近公司可能要有变革,要统计我们的技能。真的是很无语,但是有没有办法。哎,问豆包吧提起Hono大家可能很陌生,这是什么?但是我提到Expressjs、nodejs想必前端小伙伴很熟悉啊。那么Hon...
- 生活例子说明线程,简单明了(列举一个日常生活中的例子以程序的形式表示)
-
1.程序设计的目标在我看来单从程序的角度来看,一个好的程序的目标应该是性能与用户体验的平衡。当然一个程序是否能够满足用户的需求暂且不谈,这是业务层面的问题,我们仅仅讨论程序本身。围绕两点来展开,性能...
- Node实战006:自定义模块的创建和使用详解
-
Node的应用是由模块组成的,每个文件的定义都是一个模块(module变量代表当前模块)并有自己的作用域。Node遵循commonjs的模块规范,用来隔离每个模块的作用域,使每一个模块在自身的命名空间...
- Node.js基本内容和知识点(node.js的概念)
-
简单的说Node.js就是运行在服务端的JavaScript,起初段定位是后端开发语言,由于技术的不够成熟,一般小型项目会完全使用node.js作为后台支撑,大项目中,运行不够稳定,不会轻易使用...
- 干货 | 如何利用Node.js 构建分布式集群
-
引言在软件定义的世界里,企业通过Web应用和移动应用程序来提供大部分的服务,Node.js迅速成为时下最为流行的一个平台之一,就和它可以搭建响应速度快、易于扩展的web应用和移动应用有很大关系,并凭...
- nodejs mongodb 实现简易留言板(node.js留言板)
-
一个朋友问了一下mongodb的一些操作问题我就做了下面这个简单的留言板给他做一个实例希望能帮助到他express的框架就不说了express的问题请移步nodejs之expressht...
- nodejs mqtt 智能售货机系统物联网控制系统源码分享
-
智能售货机系统(Moleintelligentvendingmachinesystem)是一套物联网控制系统性的解决方案。主要涉及到的语言和库有c,c++,js,nodejs,vue.js,...
- 为什么 Node.js 这么火,而同样异步模式 Python 框架 Twisted 却十几年一直不温不火?
-
说nodejs只是靠营销的是否太天真了些?当初nodejs出来的时候各种BUG,我简单的测试其大文件传输都会出现各种问题。而同時期的其他阵营早就甩其几条街了。但是为什么却能一直不断发展壮大?...
- 2020年14个最有用的NodeJS库(node用什么数据库)
-
Express快速,简单,极简的节点Web框架对…有好处·易于处理多种类型的请求,例如GET,PUT,POST和DELETE请求·快速构建单页,多页和混合Web应用程序每周下载1100万Lice...
- 连载:2016年最好的JS框架和库(下)
-
继续上一期的介绍:Agility.jsAgility.js是专为JS服务的MVC库,你可以免费编写可再用和可维护的浏览器代码,Agility支持Js,样式(CSS)、内容(HTML)和行为(JS)。C...
- awesome-nodejs 终极资源库:60K+星标的开发者宝藏
-
Node.js终极资源库:60K+星标的开发者宝藏引言在GitHub上,有一个备受瞩目的Node.js资源仓库,以其惊人的60.6k星标量和6kfork量,成为了Node.js开发者的必备参考。这个...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 5 分钟搭建 Node.js 微服务原型(node 微服务架构)
- 当前的前端,真的不配叫程序员吗?
- 聊聊asp.net中Web Api的使用(asp.net core web api教程)
- NodeJS中,listen Access:permission denied解决办法
- Hono — 下一代高性能web框架(天融信下一代vnp)
- 生活例子说明线程,简单明了(列举一个日常生活中的例子以程序的形式表示)
- Node实战006:自定义模块的创建和使用详解
- Node.js基本内容和知识点(node.js的概念)
- 干货 | 如何利用Node.js 构建分布式集群
- nodejs mongodb 实现简易留言板(node.js留言板)
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- java框架spring (43)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)