Gin是一个基于golang的net包实现的网络框架。从github上,我们可以看到它相对于其他框架而言,具有优越的性能。本系列将从应用的角度来解析其源码。(转载请指明出于breaksoftware的csdn博客)

本文我们将分析其路由的原理。先看个例子(源于github)

func main() {// Disable Console Color// gin.DisableConsoleColor()// Creates a gin router with default middleware:// logger and recovery (crash-free) middlewarerouter := gin.Default()router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)// By default it serves on :8080 unless a// PORT environment variable was defined.router.Run()// router.Run(":3000") for a hard coded port
}

可以说,这种写法非常的优雅。第7行新建了一个路由器;第9~15行定义了路由规则;第19行启动该路由器。如此整个服务就跑起来了。

我们将重心放在路由规则这段,可以很清晰的看到或者猜测到:

  1. 这儿看到的Get、Post、Put等都是Http的协议
  2. 向http://host/someGet发送Get请求将由getting方法处理
  3. 向http://host/somePost发送Post请求将由posting方法处理
  4. ……

现在我们开始分析路由器是怎么将请求和处理方法(handler)关联起来的。

第7行创建的对象叫做路由器(router),但是其底层名称却是“引擎”(Engine)

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

关注下第5行,这儿有个中间件(midlleware)的概念。目前我们只要把它看成一个函数对象(也是handler)即可。

每个引擎(Engine)都有一个路由集合(RouterGroup)。每个路由集合都有一个默认中间件集合。

type Engine struct {RouterGroup……
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {Handlers HandlersChainbasePath stringengine   *Engineroot     bool
}

Use方法就是将Logger和Recovery中间件加入到默认的中间件集合中。之后我们会看到针对每个需要被路由的请求,这些中间件对应的handler都会被调用

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

我们再回到GET、POST这些方式上来,其底层都是调用了路由集合(RouterGroup)的handle方法

router.GET("/someGet", getting)
……
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {return group.handle("GET", 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()
}

第8行通过相对路径获取绝对路径;第9行将该路径对应的handlers和之前加入的中间件(Logger()和Recovery()返回的是一个匿名函数,即handler。之后我们会看到)的handlers合并;第10行将对absolutePath路径Get请求对应的处理方法(handlers)加入到引擎的路由中。

我们看下combineHandlers的实现。它生成一个新的handler切片,然后先把中间件的handler插入到头部,然后把用户自定义处理某路径下请求的handler插入到尾部。最后返回的是这个新生成的切片,而引擎中之前设置的中间件handlers(group.Handlers)并没改变。所以针对每个需要被路由的请求,之前注册的中间件对应的handler都会被调用

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
}

再看下addRoute干了什么

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {……root := engine.trees.get(method)if root == nil {root = new(node)engine.trees = append(engine.trees, methodTree{method: method, root: root})}root.addRoute(path, handlers)
}

引擎的trees是一个多维切片。每个请求方法都有对应的一个methodTree,比如Get类型请求就只有一个methodTree与其对应。

每种请求方式(Get、Post等)又有很多路径与其对应。每个路径是一个node结构,该结构的handlers保存了如何处理该路径下该请求方式的方法集合。

所以第3~7行先尝试获取请求方式的结构体。没找到就创建一个。最后在第8行将路径和处理方法的对应关系加入到该请求方式结构之下。

type node struct {path      stringindices   stringchildren  []*nodehandlers  HandlersChainpriority  uint32nType     nodeTypemaxParams uint8wildChild bool
}type methodTree struct {method stringroot   *node
}type methodTrees []methodTree

我们看到node结构下还有一个node的切片,这意味着这是一个递归结构。当然,我们通俗的称为叶子节点可能更容易理解点。为什么会有叶子节点这个概念?举个例子

 r.GET("/pi", func(c *gin.Context) {c.String(http.StatusOK, "po")})r.GET("/pin", func(c *gin.Context) {c.String(http.StatusOK, "pon")})r.GET("/ping", func(c *gin.Context) {c.String(http.StatusOK, "pong")})

/ping的父节点的path是/pin,/pin的父节点的path是/pi。如果我们再增加一个/pingabc,那么它的父节点path就是/ping。这些节点都有对应的handlers。

方式、路径和处理函数方法的映射准备好后,我们再看看Gin是如何驱动它们运行的。这个时候我们就要看

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)err = http.ListenAndServe(address, engine)return
}

Gin的底层使用了net/http包。只是它封装了Engine结构体,并且让它实现了Handler接口

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context)c.writermem.reset(w)c.Request = reqc.reset()engine.handleHTTPRequest(c)engine.pool.Put(c)
}

ServeHTTP方法会在serve方法中调用,serve会被Serve调用。在Serve中,我们看到接受请求和处理请求的逻辑了。Serve最终会在ListenAndServe中被调用,而它就是在引擎(Engine)的Run中被调用了的。这样我们只要关注引擎(Engine)的handleHTTPRequest实现即可。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
……serverHandler{c.server}.ServeHTTP(w, w.req)
……
}func (srv *Server) Serve(l net.Listener) error {
……for {rw, e := l.Accept()
……tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx)}
}func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

handleHTTPRequest方法会找到当前请求方式对应methodTree。然后找到路径对应的处理方法

func (engine *Engine) handleHTTPRequest(c *Context) {httpMethod := c.Request.Methodpath := c.Request.URL.Path
……// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// Find route in treehandlers, params, tsr := root.getValue(path, c.Params, unescape)if handlers != nil {c.handlers = handlersc.Params = paramsc.Next()c.writermem.WriteHeaderNow()return}
……

第17行Next方法,将驱动相应的处理函数执行

func (c *Context) Next() {c.index++for s := int8(len(c.handlers)); c.index < s; c.index++ {c.handlers[c.index](c)}
}

这儿我们注意下,处理函数的参数是Context指针!!调用Next是这个Context,然后handler处理的还是这些Context。比较反常的是,handler内部还可能调用该Context的Next方法!!!是不是感觉绕到一个循环里去了。我们回顾下之前中间件Logger

func Logger() HandlerFunc {return LoggerWithWriter(DefaultWriter)
}func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
……return func(c *Context) {
……// Process requestc.Next()
……}
}

是不是有点混乱?

其实不会出错,因为Next方法没有使用局部变量去遍历计数handlers的,它使用了和Context的成员变量index。这样就可以保证某些情况下Next()函数不会触发任何handler的调用。

Gin源码解析和例子——路由相关推荐

  1. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  2. gin 源码解析 - 详解http请求在gin中的流转过程

    本篇文章是 gin 源码分析系列的第二篇,这篇文章我们主要弄清一个问题:一个请求通过 net/http 的 socket 接收到请求后, 是如何回到 gin 中处理逻辑的? 我们仍然以 net/htt ...

  3. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  4. gin源码解析(1) - gin 与 net/http 的关系

    gin是目前Go里面使用最广泛的框架之一了,弄清楚gin框架的原理,有助于我们更好的使用gin.这个系列gin源码阅读会逐步讲明白 gin 的原理,欢迎关注后续文章. gin 概览 想弄清楚 gin, ...

  5. Dubbo源码解析之服务路由策略

    1. 简介 服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者.在详细分析服务路由的源码之前,先来介绍一下服务路由是什么.服务路由包含一条 ...

  6. vue-router 源码解析(三)-实现路由守卫

    文章目录 基本使用 导语 初始化路由守卫 useCallbacks 发布订阅模式管理路由守卫 push 开始导航 resolve返回路由记录匹配结果 navigate 开始守卫 路由守卫前置方法 抽取 ...

  7. Go框架 gin 源码学习--路由的实现原理剖析

    往期回顾: gin源码解析 - gin 与 net/http 的关系 gin 源码解析 - 详解http请求在gin中的流转过程 上面两篇文章基本讲清楚了 Web Server 如何接收客户端请求,以 ...

  8. 详细讲解go web框架之gin框架源码解析记录及思路流程和理解

    开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...

  9. 前端单页路由《stateman》源码解析

    <stateman>是波神的一个超级轻量的单页路由,拜读之后写写自己的小总结. stateman的github地址 github.com/leeluolee/s- 简单使用 以下文章全部以 ...

最新文章

  1. y电容如何选型_陶瓷气体放电管,如何选型?
  2. 开源工具高效分析Java应用
  3. git 生成patch和使用patch
  4. alexa语音实现_如何通过语音删除Alexa录音
  5. bzoj 5369: [Pkusc2018]最大前缀和
  6. 技术交流论坛_天气预报|“第一届国家建筑工程与材料测试技术论坛”暨“第七届全国建筑材料测试技术”交流会...
  7. asyncdata 获取参数_nuxt的asyncData发送post请求如何传递FormData形式的参数
  8. 2021数学建模B题 空气质量二次模型
  9. wind 数据 python_从wind python接口获取数据并存储
  10. 怎样把mp4视频转换成mov格式电影
  11. 免费错别字检测、在线纠错工具
  12. 用计算机进行文本信息的加工处理,前面我们已经学了用计算机进行文本信息的加工与表达过程.ppt...
  13. python 离群值_python:删除离群值操作(每一行为一类数据)
  14. 12306查询车票(爬虫小练_1)
  15. Linux 文件管理-基础知识-文件隐藏属性-【chattr】-排查系统异常命令
  16. 图解ARP协议(三)ARP防御篇-如何揪出内鬼并优雅的还手
  17. matlab系统函数伯德图,利用matlab画出根轨迹图|伯德图bode
  18. Gym - 101612A 点亮数字
  19. 【openVINO+paddle】CPU部署新冠肺炎CT图像分类识别与病害分割
  20. 我的世界古代战争模组介绍java版_我的世界古代战争2mod教程零基础到专属军队...

热门文章

  1. 用代码优雅的终止springboot服务
  2. 使用Python,OpenCV加载图像并将其显示在屏幕上?
  3. PCL两种方式的点云读写
  4. 【目标检测】(8) ASPP改进加强特征提取模块,附Tensorflow完整代码
  5. linux系统重装后挂载数据盘,Linux重装系统后如何重新挂载数据盘?
  6. 学习java周期_Java第一作业周期总结
  7. netcore读取json文件_.net core读取json格式的配置文件
  8. PCL common中常见的基础功能函数
  9. 在Mac上使用pip3安装Jupyter Notebook并简单使用
  10. 在CentOS 6.6上搭建OpenResty 1.9.7.4并输出示例