一、简介
Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。
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的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点。
对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。
借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。
二、安装与使用入门
安装:前提需要先安装好go1.15+,然后执行如下命令即完成安装。
|
|
go get -u github.com/gin-gonic/gin |
使用:启动一个gin服务,只需要简单的几行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 // gin 框架中采用的路由库是基于httprouter做的 router := gin.Default() // 2.绑定路由规则,执行的函数 <strong><span style="color: #ff0000;"> // gin.Context,封装了request和response </span></strong> router.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, "hello World! I am gin!") }) // 3.监听端口,默认在8080。Run("里面不指定端口号默认为8080") router.Run(":8080") } |
Gin支持路由、路由分组、中间件、会话控制、数据解析与绑定、参数验证、渲染等等功能,几乎能满足日常开发的所有需要。下面以路由分组、中间件举例,更多可参考文章开头的相关链接。
路由分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 路由组1 ,处理GET请求 v1 := r.Group("/v1") // {} 是书写规范 { v1.GET("/login", login) v1.GET("/submit", submit) } // 路由组2 ,处理POST请求 【可以省略/】 v2 := r.Group("v2") { v2.POST("login", login) v2.POST("submit", submit) } r.Run(":8000") } func login(c *gin.Context) { name := c.DefaultQuery("name", "tom") c.String(200, fmt.Sprintf("hello %s\n", name)) } func submit(c *gin.Context) { name := c.DefaultQuery("name", "jerry") c.String(200, fmt.Sprintf("hello %s\n", name)) } |
中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
package main import ( "fmt" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.Use(MiddleWare()) router.GET("/middle", func(c *gin.Context) { // 取值 req, _ := c.Get("request") fmt.Println("request:", req) // 页面接收 c.JSON(200, gin.H{"request": req}) }) router.Run() } // 定义中间件 func MiddleWare() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() fmt.Println("中间件开始执行了") // 设置变量到Context的key中,可以通过Get()取 c.Set("request", "我是中间件") // 执行函数 c.Next() <span style="color: #ff0000;"> // 中间件执行完后续的一些事情</span>(ps:更像是java aop) status := c.Writer.Status() fmt.Println("中间件执行完毕", status) t2 := time.Since(t) fmt.Println("time:", t2) } } // consle控制台输出结果: 中间件开始执行了 request: 我是中间件 中间件执行完毕 200 time: 475.3µs [GIN] 2022/07/16 - 11:52:59 | 200 | 994.3µs | ::1 | GET "/middle" |
常用中间件推荐:
RestGate - REST API端点的安全身份验证
staticbin - 用于从二进制数据提供静态文件的中间件/处理程序
gin-cors - CORS杜松子酒的官方中间件
gin-csrf - CSRF保护
gin-health - 通过gocraft/health报告的中间件
gin-merry - 带有上下文的漂亮 打印 错误的中间件
gin-revision - 用于Gin框架的修订中间件
gin-jwt - 用于Gin框架的JWT中间件
gin-sessions - 基于mongodb和mysql的会话中间件
gin-location - 用于公开服务器的主机名和方案的中间件
gin-nice-recovery - 紧急恢复中间件,可让您构建更好的用户体验
gin-limit - 限制同时请求;可以帮助增加交通流量
gin-limit-by-key - 一种内存中的中间件,用于通过自定义键和速率限制访问速率。
ez-gin-template - gin简单模板包装
gin-hydra - gin中间件Hydra
gin-glog - 旨在替代Gin的默认日志
gin-gomonitor - 用于通过Go-Monitor公开指标
gin-oauth2 - 用于OAuth2
static gin框架的替代静态资产处理程序。
xss-mw - XssMw是一种中间件,旨在从用户提交的输入中“自动删除XSS”
gin-helmet - 简单的安全中间件集合。
gin-jwt-session - 提供JWT / Session / Flash的中间件,易于使用,同时还提供必要的调整选项。也提供样品。
gin-template - 用于gin框架的html / template易于使用。
gin-redis-ip-limiter - 基于IP地址的请求限制器。它可以与redis和滑动窗口机制一起使用。
gin-method-override - _method受Ruby的同名机架启发而被POST形式参数覆盖的方法
gin-access-limit - limit-通过指定允许的源CIDR表示法的访问控制中间件。
gin-session - 用于Gin的Session中间件
gin-stats - 轻量级和有用的请求指标中间件
gin-statsd - 向statsd守护进程报告的Gin中间件
gin-health-check - check-用于Gin的健康检查中间件
gin-session-middleware - 一个有效,安全且易于使用的Go Session库。
ginception - 漂亮的例外页面
gin-inspector - 用于调查http请求的Gin中间件。
gin-dump - Gin中间件/处理程序,用于转储请求和响应的标头/正文。对调试应用程序非常有帮助。
go-gin-prometheus - Gin Prometheus metrics exporter
ginprom - Gin的Prometheus指标导出器
gin-go-metrics - Gin middleware to gather and store metrics using rcrowley/go-metrics
ginrpc - Gin 中间件/处理器自动绑定工具。通过像beego这样的注释路线来支持对象注册
三、Gin与net/http
实际上,Go的net/http包基本上提供了从服务请求到服务响应的全套服务。
原生ntp/http代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main import ( "fmt" "net/http" ) // 原生ntp/http请求 func main() { // 查看路由注册源码,可以发现原生路由注册非常简单 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World")) }) // 服务监听及响应 //【源码重点】建立socket,取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端 if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Println("start http server fail:", err) } } |
从net/http源码学习,服务监听与响应的大致流程如下:

|
|
ln, err := net.Listen(“tcp”, addr) 做了初试化了socket、bind、listen的操作。 rw, err := l.Accept() 进行accept,等待客户端进行连接。 <span style="color: #ff0000;">go c.serve(connCtx) 启动新的goroutine来处理本次请求。同时主goroutine继续等待客户端连接,进行高并发操作。 </span>h, _ := mux.Handler® 获取注册的路由,然后拿到这个路由的handler,然后将处理结果返回给客户端。 |
既然 net/http基本上提供了全套的服务,那为什么还需要类似于gin的web框架。
从net/http的路由匹配规则来看:net/http的路由匹配根本就不符合 RESTful 的规则,遇到复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
// Find a handler on a handler map given a path string. // Most-specific (longest) pattern wins. func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" } |
所以基本所有的go web框架干的最主要的一件事情就是重写net/http的route。
甚至可以直接说 gin就是一个 httprouter 也不过分,当然gin也提供了其他比较主要的功能。
综上,net/http基本已经提供http服务的大部分功能,那些号称贼快的go框架,基本上都是提供一些功能,让我们能够更好的处理客户端发来的请求。
自定义路由示例【重写route简易版】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
package main import ( "fmt" "net/http" ) // MyMux 自定义简易路由 type MyMux struct { } // 重写route func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sayhelloName(w, r) return } http.NotFound(w, r) return } func sayhelloName(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello myroute!") } func main() { mux := &MyMux{} http.ListenAndServe(":9090", mux) } |
四、Gin路由原理
前面说过,Gin框架实际上就是一个route路由重写框架,那么Gin是如果引入路由、生成路由树、进行路由查找的?
1、路由引入
gin启动server服务【router.run()】的底层依然是 http.ListenAndServe(),所以 gin 建立 socket 的过程,accept 客户端请求的过程与 net/http 没有差别,会同样重复上面的过程。唯一有差别的位置就是在于获取 ServeHTTP 的位置。

http请求路由引入源码流程
|
|
//由于 Handler 是 interface 类型,其真正的类型是 gin.Engine,根据 interace 的动态转发特性,最终会跳转到 gin.Engine.ServeHTTP 函数中来 // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) //从 sync.pool 里面拿去一块内存 c.writermem.reset(w) //对这块内存做初始化工作,防止数据污染 c.Request = req c.reset() engine.handleHTTPRequest(c) //处理请求 handleHTTPRequest engine.pool.Put(c) //请求处理完成后,把这块内存归还到 sync.pool 中 } |
2、路由注册
gin 框架中采用的路由库是基于httprouter做的,httprouter会将所有路由规则构造一颗前缀树。
|
|
其实,gin注册路由的实现很简单,不同的 方法就是一棵路由树,所以当 gin 注册路由的时候,会根据不同的 Method 分别注册不同的路由树。 如: GET /user/{userID} HTTP/1.1 POST /user/{userID} HTTP/1.1 PUT /user/{userID} HTTP/1.1 DELETE /user/{userID} HTTP/1.1 |
路由注册流程【生成路由树的源码流程】
|
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { // ... ... root := engine.trees.get(method) //拿到一个 method 方法时,去 trees slice 中遍历 if root == nil { //如果没有找到,则重新创建一颗新的方法树出来, 然后将 URL对应的 handler 添加到这个路由 树上 root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) //如果 trees slice 存在这个 method, 则这个URL对应的 handler 直接添加到找到的路由树上 // ... ... } |
路由树
|
|
type node struct { path string indices string wildChild bool nType nodeType priority uint32 children []*node // child nodes, at most 1 :param style node at the end of the array handlers HandlersChain fullPath string } |
当然最简单最粗暴的就是每个字符串占用一个树的叶子节点,不过这种设计会带来的问题:占用内存会升高,实际上Gin采用了共用前缀方式构建树。如: abc, abd, af 都是用共同的前缀的,如果能共用前缀的话,可以省内存空间。
3、路由查找
当 gin 收到客户端的请求时,第一件事就是去路由树里面去匹配对应的 URL,找到相关的路由,拿到相关的处理函数。其实这个过程就是 handleHTTPRequest 要干的事情。
路由查找源码流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
func (engine *Engine) handleHTTPRequest(c *Context) { // ... ... // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree value := root.getValue(rPath, c.params, c.skippedNodes, unescape) if value.params != nil { c.Params = *value.params } if value.handlers != nil { c.handlers = value.handlers c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && 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.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } |
————————————————
版权声明:本文为CSDN博主「进击的程序猿~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41822345/article/details/125819016
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - go Gin框架原理