Gin框架底层原理

目录

1.Gin 的背景

2.Gin 与 net/http 的关系

3.Gin 框架使用示例

4.注册 handler 流程

4.1.核心数据结构

gin.Engine

RouterGroup

HandlersChain

4.2.流程

4.3.初始化 Engine

 4.4.注册 middleware

4.5.注册 handler

完整路径拼接

完整 handlers 生成

注册 handler 到路由树

4.6.中间件中的next原理

gin 中间件的工作原理

next 函数的实现

gin 内部实现

5.启动服务流程

5.1.流程入口

5.2 启动服务

5.3 处理请求


1.Gin 的背景

Gin 是 Golang 世界里最流行的 web 框架,于 github 开源:https://github.com/gin-gonic/gin

其中本文涉及到的源码走读部分,代码均取自 gin tag:v1.9.0 版本。

主要优点:

  • 支持中间件操作( handlersChain 机制 )

  • 更方便的使用( gin.Context )

  • 更强大的路由解析能力( radix tree 路由树 )

2.Gin 与 net/http 的关系

Gin 是在 Golang HTTP 标准库 net/http 基础之上的再封装

3.Gin 框架使用示例

下面提供一段接入 Gin 的示例代码:

  • 构造 gin.Engine 实例:gin.Default()

  • 路由组注册中间件:Engine.Use()

  • 路由组注册 POST 方法下的 handler:Engine.POST()

  • 启动 http server:Engine.Run()

go get -u github.com/gin-gonic/gin
package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	// 创建一个默认的 gin 路由器
	router := gin.Default()

	// 注册中间件
	//router.Use(MiddleWare)

	// 设置一个路由处理器
	router.GET("/", func(c *gin.Context) {
		c.String(200, "Hello, World!")
	})

	// 设置 POST 请求处理器
	router.POST("/post", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "This is a POST request",
		})
	})

	// 启动 HTTP 服务器
	router.Run(":8080") // 监听在 8080 端口
}

4.注册 handler 流程

4.1.核心数据结构

gin.Engine

type Engine struct {
   // 路由组
    RouterGroup
    // ...
    // context 对象池
    pool             sync.Pool
    // 方法路由树
    trees            methodTrees
    // ...
}
// net/http 包下的 Handler interface
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}


func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // ...
}

Engine 为 Gin 中构建的 HTTP Handler,其实现了 net/http 包下 Handler interface 的抽象方法: Handler.ServeHTTP,因此可以作为 Handler 注入到 net/http 的 Server 当中.

Engine包含的核心内容包括:

  • 路由组 RouterGroup

  • Context 对象池 pool:基于 sync.Pool 实现,作为复用 gin.Context 实例的缓冲池. gin.Context 的内容于本文第 5 章详解

  • 路由树数组 trees:共有 9 棵路由树,对应于 9 种 http 方法. 路由树基于压缩前缀树实现。

9 种 http 方法展示如下:

const (
    MethodGet     = "GET"
    MethodHead    = "HEAD"
    MethodPost    = "POST"
    MethodPut     = "PUT"
    MethodPatch   = "PATCH" // RFC 5789
    MethodDelete  = "DELETE"
    MethodConnect = "CONNECT"
    MethodOptions = "OPTIONS"
    MethodTrace   = "TRACE"
)

RouterGroup

type RouterGroup struct {
    Handlers HandlersChain
    basePath string
    engine *Engine
    root bool
}

RouterGroup 是路由组的概念,其中的配置将被从属于该路由组的所有路由复用:

  • Handlers:路由组共同的 handler 处理函数链. 组下的节点将拼接 RouterGroup 的公用 handlers 和自己的 handlers,组成最终使用的 handlers 链

  • basePath:路由组的基础路径. 组下的节点将拼接 RouterGroup 的 basePath 和自己的 path,组成最终使用的 absolutePath

  • engine:指向路由组从属的 Engine

  • root:标识路由组是否位于 Engine 的根节点. 当用户基于 RouterGroup.Group 方法创建子路由组后,该标识为 false

HandlersChain

type HandlersChain []HandlerFunc


type HandlerFunc func(*Context)

HandlersChain 是由多个路由处理函数 HandlerFunc 构成的处理函数链. 在使用的时候,会按照索引的先后顺序依次调用 HandlerFunc. 

4.2.流程

下面以创建 gin.Engine 、注册 middleware 和注册 handler 作为主线,进行源码走读和原理解析

func main() {
	// 创建一个默认的 gin 路由器
	router := gin.Default()

	// 注册中间件
	router.Use(MiddleWare)

	// 设置 POST 请求处理器
	router.POST("/post", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "This is a POST request",
		})
	})

	// ...
}

4.3.初始化 Engine

方法调用:gin.Default -> gin.New

  • 创建一个 gin.Engine 实例

  • 创建 Enging 的首个 RouterGroup,对应的处理函数链 Handlers 为 nil,基础路径 basePath 为 "/",root 标识为 true

  • 构造了 9 棵方法路由树,对应于 9 种 http 方法

  • 创建了 gin.Context 的对象池

func Default() *Engine {
    engine := New()
    // ...
    return engine
}
func New() *Engine {
    // ...
    // 创建 gin Engine 实例
    engine := &Engine{
        // 路由组实例
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        // ...
        // 9 棵路由压缩前缀树,对应 9 种 http 方法
        trees:                  make(methodTrees, 0, 9),
        // ...
    }
    engine.RouterGroup.engine = engine     
    // gin.Context 对象池   
    engine.pool.New = func() any {
        return engine.allocateContext(engine.maxParams)
    }
    return engine
}

 4.4.注册 middleware

通过 Engine.Use 方法可以实现中间件的注册,会将注册的 middlewares 添加到 RouterGroup.Handlers 中. 后续 RouterGroup 下新注册的 handler 都会在前缀中拼上这部分 group 公共的 handlers.

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

4.5.注册 handler

以 http post 为例,注册 handler 方法调用顺序为 RouterGroup.POST-> RouterGroup.handle,接下来会完成三个步骤:

  • 拼接出待注册方法的完整路径 absolutePath

  • 拼接出代注册方法的完整处理函数链 handlers

  • 以 absolutePath 和 handlers 组成 kv 对添加到路由树中

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

完整路径拼接

结合 RouterGroup 中的 basePath 和注册时传入的 relativePath,组成 absolutePath

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}
func joinPaths(absolutePath, relativePath string) string {
    if relativePath == "" {
        return absolutePath
    }


    finalPath := path.Join(absolutePath, relativePath)
    if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
        return finalPath + "/"
    }
    return finalPath
}

完整 handlers 生成

深拷贝 RouterGroup 中 handlers 和注册传入的 handlers,生成新的 handlers 数组并返回

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

注册 handler 到路由树

  • 获取 http method 对应的 methodTree

  • 将 absolutePath 和对应的 handlers 注册到 methodTree 中

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    // ...
    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)
    // ...
}

4.6.中间件中的next原理

gin 框架中,next 函数实际上是一个闭包,它代表了中间件链中的下一个中间件或最终的路由处理器。next 函数是在 gin 框架内部生成的,并通过 gin.HandlerFunc 类型传递给中间件函数。下面我将详细介绍 next 函数的实现原理。

gin 中间件的工作原理

  1. 定义中间件:

    • 中间件函数是一个接受 gin.HandlerFunc 参数的函数。
    • 这个函数返回一个 gin.HandlerFunc,它包含了中间件的逻辑。
  2. 注册中间件:

    • 使用 router.Use 方法来注册中间件。
    • 这些中间件按照注册顺序排列,形成一个中间件栈。
  3. 调用中间件:

    • 当请求到达时,gin 框架会从中间件栈的第一个中间件开始执行。
    • 每个中间件都会调用 next(c) 来执行下一个中间件或路由处理器。
    • next(c) 是一个闭包,它保存了中间件栈的信息,并负责调用下一个中间件。

next 函数的实现

gin 框架内部,next 函数是通过闭包实现的。下面是一个简化的示例,展示 next 函数是如何在 gin 框架中实现的:

// 定义一个中间件
func loggingMiddleware(next gin.HandlerFunc) gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("Request received at:", c.Request.URL.Path)
        
        // 调用下一个中间件或处理函数
        next(c)

        fmt.Println("Response sent for:", c.Request.URL.Path)
    }
}

gin 内部实现

gin 框架内部,中间件栈和 next 函数的实现更加复杂。下面是一个简化的示例,展示 gin 如何实现中间件栈和 next 函数:

// 假设这是简化版的 gin.HandlerFunc 类型
type ginFunc func(*gin.Context)

// 假设这是简化版的 gin.Context 类型
type ginContext struct{}

// 假设这是简化版的 gin.RouterGroup 类型
type ginRouterGroup struct {
    middlewares []ginFunc
}

// Use 方法注册中间件
func (rg *ginRouterGroup) Use(middlewares ...ginFunc) {
    rg.middlewares = append(rg.middlewares, middlewares...)
}

// Handle 方法设置路由处理器
func (rg *ginRouterGroup) Handle(method string, path string, handlers ...ginFunc) {
    // 假设这里有代码来处理路由和处理器的绑定
}

// Next 是一个闭包,它保存了中间件栈的信息
func (c *ginContext) Next() {
    // 这里假设有一个方法来执行下一个中间件
}

// 一个示例中间件
func loggingMiddleware(next ginFunc) ginFunc {
    return func(c *ginContext) {
        fmt.Println("Before next()")
        next(c) // 调用下一个中间件或处理函数
        fmt.Println("After next()")
    }
}

func main() {
    router := &ginRouterGroup{}

    // 注册中间件
    router.Use(loggingMiddleware)

    // 设置一个路由处理器
    router.Handle("GET", "/", func(c *ginContext) {
        c.Next()
    })

    // 启动 HTTP 服务器
    // 这里假设有一个启动服务器的方法
}

gin 框架中,next 函数是一个闭包,它负责调用下一个中间件或处理函数。这个闭包是 gin 框架内部实现的一部分,用于维护中间件栈的执行流程。当你在中间件函数中调用 next(c) 时,实际上是调用了 gin 框架提供的闭包,这个闭包知道如何调用下一个中间件或处理函数。 

5.启动服务流程

5.1.流程入口

下面通过 Gin 框架运行 http 服务为主线,进行源码走读:

func main() {
    // 创建一个 gin Engine,本质上是一个 http Handler
    mux := gin.Default()
    
    // 一键启动 http 服务
    if err := mux.Run(); err != nil{
        panic(err)
    }
}

5.2 启动服务

一键启动 Engine.Run 方法后,底层会将 gin.Engine 本身作为 net/http 包下 Handler interface 的实现类,并调用 http.ListenAndServe 方法启动服务.

func (engine *Engine) Run(addr ...string) (err error) {
    // ...
    err = http.ListenAndServe(address, engine.Handler())
    return
}

顺便多提一嘴,ListenerAndServe 方法本身会基于主动轮询 + IO 多路复用的方式运行,因此程序在正常运行时,会始终阻塞于 Engine.Run 方法,不会返回.

func (srv *Server) Serve(l net.Listener) error {
   // ...
   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, err := l.Accept()
        // ...
        connCtx := ctx
        // ...
        c := srv.newConn(rw)
        // ...
        go c.serve(connCtx)
    }
}

5.3 处理请求

在服务端接收到 http 请求时,会通过 Handler.ServeHTTP 方法进行处理. 而此处的 Handler 正是 gin.Engine,其处理请求的核心步骤如下:

  • 对于每笔 http 请求,会为其分配一个 gin.Context,在 handlers 链路中持续向下传递

  • 调用 Engine.handleHTTPRequest 方法,从路由树中获取 handlers 链,然后遍历调用

  • 处理完 http 请求后,会将 gin.Context 进行回收. 整个回收复用的流程基于对象池管理

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // 从对象池中获取一个 context
    c := engine.pool.Get().(*Context)
    
    // 重置/初始化 context
    c.writermem.reset(w)
    c.Request = req
    c.reset()
    
    // 处理 http 请求
    engine.handleHTTPRequest(c)


    // 把 context 放回对象池
    engine.pool.Put(c)
}

Engine.handleHTTPRequest 方法核心步骤分为三步:

  • 根据 http method 取得对应的 methodTree

  • 根据 path 从 methodTree 中找到对应的 handlers 链

  • 将 handlers 链注入到 gin.Context 中,通过 Context.Next 方法按照顺序遍历调用 handler

func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    rPath := c.Request.URL.Path
    
    // ...
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        // 获取对应的方法树
        if t[i].method != httpMethod {
            continue
        }
        root := t[i].root
        // 从路由树中寻找路由
        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
        }
        // ...
        break
    }
    // ...
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值