详解http请求在gin中的流转过程

560 阅读3分钟

1、数据如何在gin中流转

package main

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

func main() {
    // 创建 Gin Engine 实例
    r := gin.Default()

    // 设置请求 URI /ping 的路由及响应处理函数
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    // 启动 Web 服务,监听端口,等待 HTTP 请求到并生成响应
    r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

这段代码的大概流程:

  1. r := gin.Default()初始化了相关的参数,创建Gin实例
  2. /ping将路由及处理handler注册到路由树中
  3. 启动服务

r.Run()其实调用的是err = http.ListenAndServe(address, engine), gin其实利用了net/http的处理过程,将路由连接到http.Server启动并监听HTTP请求。

2、Engine

在整个gin框架中最重要的一个struct就是Engine, 它包含路由, 中间件, 相关配置信息等. Engine的代码主要就在gin.go中。

Engine中比较重要的几个属性, 其他的属性暂时全部省略掉

type Engine struct {
    RouterGroup // 路由
    pool             sync.Pool  // context pool
    trees            methodTrees // 路由树
    // html template及其他相关属性先暂时忽略
}

engine有几个比较主要的函数:

New(), Default()

func New() *Engine {
    // ...
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        // ...
        trees: make(methodTrees, 0, 9),
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

New()主要干的事情:

  • 初始化了Engine
  • RouterGroupHandlers(数组)设置成nil, basePath设置成/
  • 为了使用方便, RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroupengine指针
  • 为了防止频繁的context GC造成效率的降低, 在Engine里使用了sync.Pool, 专门存储ginContext
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

Default()跟New()几乎一模一样, 就是调用了gin内置的Logger(), Recovery()中间件。

Use()

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

Use()就是gin的引入中间件的入口了. 仔细分析这个函数, 不难发现Use()其实是在给RouteGroup引入中间件的。

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)
}

这段代码就是利用method, path, 将handlers注册到engine的trees中. 当 addRoute 时,先根据 method 找到对应的 tree (Engine.trees[i])。然后会比较 加入者 的 path 和 node 中的 path 相似的部分,相似的部分 作为 父结点,不同的部分作为 子结点。以 多叉树 的方式存储下来。

这里会把 URL 中的路由变量也当作字符串存入树中,因为相同 URL 他们的变量也是一样的。

3、Run系列函数

Run, RunTLS, RunUnix, RunFd这些函数其实都是最终在调用net/httphttp服务。

gin.Runnet/http标准库http.ListenAndServe的简写,功能是将路由连接到http.Server启动并监听HTTP请求。

func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	if engine.isUnsafeTrustedProxies() {
		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
	}

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine.Handler())
	return
}
  • address := resolveAddress(addr)将传入的addr进行判断,返回正确的端口。
  • 调用http.ListenAndServe 对这个端口进行监听,并将框架的信息引擎传进入。

ServeHTTP

这个函数相当重要了, 主要有这个函数的存在, 才能将请求转到gin中, 使用gin的相关函数处理request请求。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
        // 创建上下文对象,注意这里是gin封装的Context,并不是go原生的Context!
        // 这里用到了sync.Pool来进行内存的复用,防止频繁创建上下文,而导致性能的下降
	c.writermem.reset(w)
	c.Request = req
        // 对请求进行赋值,并将这个req请求放到context的Request上下文中。
	c.reset()

	engine.handleHTTPRequest(c)// 处理请求

	engine.pool.Put(c)// 对上下文对象进行回收
}

至此,终于我们看到了 gin.ServeHTTP 的全貌了

  1. 从 sync.pool 里面拿去一块内存
  2. 对这块内存做初始化工作,防止数据污染
  3. 处理请求 handleHTTPRequest
  4. 请求处理完成后,把这块内存归还到 sync.pool 中

本文正在参加技术专题18期-聊聊Go语言框架