Gin(二)深度解析 HelloWorld,尝尝核心代码的滋味

内容纲要

目的

从最简单的 HelloWorld(实际上并没有输出 HelloWorld 😁) 入手,分析 Gin 的执行过程。

HelloWorld Demo

package main

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

func main()  {
    engine := gin.Default()
    engine.Run()  // 监听并在 0.0.0.0:8080 上启动服务
}

分析

gin.Default()

// gin.Default() 代码

// 返回一个启用 Recovery 中间件 和 Logger 中间件的 Engine 实例
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

注释分析

  • 分析三个英文名词
    • Recovery:根据名词只知道是一个中间件,不知道具体作用;先忽略
    • Logger:从名词可以看出,应该是跟日志相关的中间件;先不深入研究
    • Engine:行业通常翻译为“引擎”,从名词可以大概看出,这个东西应该是整个 Gin 的核心;是需要重点分析的部分

代码初步分析

  • 函数名被注释解释的很清楚了:返回 Engine 实例的指针
  • 第 5 行
    • debugPrintWARNINGDefault():目测是一个 debug 打印的函数(进入代码后可以看到确实是,功能为判断 Gin 的最小支持的 Go 版本)
  • 第 6 行
    • New(Go 的常规写法) 一个实例(跟踪 New() 可以看到是 Engine 结构体的实例化方法)
  • 第 7 行
    • 根据 Use 函数的名称结合之前的注释,可以推断出这个函数的目的是把 Recovery 中间件和 Logger 中间件插入到 Engine 中(Use 函数的参数是一个可变长参数,可以判断这个函数可以接收无数个中间件,进一步判断出 Gin 可能有好多个中间件,甚至可以自己开发中间件)
  • 返回行返回实例指针

由之前初步分析,我们可以得出比较关键的信息:

  • Engine

Engine 结构体预览

这部分较长,只需要初步看一遍,对各个变量有一定的认识就行(混眼熟)。

// New 实例的默认值:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true

type Engine struct {
    // 路由组:分组组织管理路由( 这里相当于 Engine 继承 RouterGroup)
    RouterGroup

    // URL 路径末尾带或不带‘/’的请求,如果路由表中匹配不到对应的路径,则值为:
    //     true:自动重定向到路由表中带或不带‘/’的路径
    //         example:
    //             [路由:/foo]  [请求:/foo 或 /foo/]  [结果:都指向 /foo]
    //             [路由:/foo/]  [请求:/foo 或 /foo/]  [结果:都指向 /foo/]
    //     false:不自动重定向
    RedirectTrailingSlash bool

    // 当路径在路由表中匹配不到时,值为:
    //     true:尝试修复路径
    //         example:
    //             [路由:/foo]  [请求:../../foo]  [结果:指向 /foo]
    //             [路由:/foo/]  [请求:../../foo]  [结果:指向 /foo/]
    //     false:不修复
    RedirectFixedPath bool

    // 匹配不到对应的路由时,是否允许检查其它 Method 方法,值为:
    //     true:允许检查(返回 405:Method not allowed)
    //         example:
    //             [路由:GET /foo]  [请求:PUT /foo]  [结果:返回 405:Method not allowed]
    //     false:不检查(返回 404:page not found)
    //      example:
    //             [路由:GET /foo]  [请求:PUT /foo]  [结果:返回 404:Page not found]
    HandleMethodNotAllowed bool

    // 是否转发客户端 IP
    //     true:转发
    //     false:不转发
    ForwardedByClientIP    bool

    // 是否在请求头中插入 X-AppEngine... (跟 PaaS 结合)
    //     true:插入
    //     false:不插入
    AppEngine bool

    // 是否使用 url.RawPath 查找参数
    //     true:使用
    //     false:不使用(默认使用 url.Path)
    UseRawPath bool

    // 请求是否转义
    //     true:不转义(保留 url.Path)(如果 UseRawPath == false:UnescapePathValues 为 true)
    //     false:转义
    UnescapePathValues bool

    // maxMemory 的值(http.Request 的 ParseMultiparForm 调用时的参数)
    MaxMultipartMemory int64

    // 是否可用从有额外‘/’的 URL 中解析参数
    //     true:可以
    //     false:不可以
    RemoveExtraSlash bool

    // 表示一组用于 HTML 模板渲染的分隔符(见 html/template 库)
    delims           render.Delims

    // 在 context.SecureJSON 中使用的前缀
    secureJsonPrefix string

    // HTMLRender 接口(用于渲染 HTMLProduction、HTMLDebug 的模板)
    HTMLRender       render.HTMLRender

    // 用于映射函数名和函数(在 text/template 包中)
    FuncMap          template.FuncMap

    // 找不到路由?(留坑待补)
    allNoRoute       HandlersChain

    // 找不到请发方法?(留坑待补)
    allNoMethod      HandlersChain

    // 找不到路由?(留坑待补)
    noRoute          HandlersChain

    // 找不到请发方法?(留坑待补)
    noMethod         HandlersChain

    // (留坑待补)
    pool             sync.Pool

    // methodtrees 包含一组请求方法和 Node(Node 管理 Path 节点树) 指针的结构体
    trees            methodTrees
}

engine.Run()

看过 Engine 后,我们对它的一些变量有了初步的认识,也能从中推断出一些用处。然后我们再把视线转回 HelloWorld Demo 的第 12 行来。

// 将 Engine 的路由挂载到 http.Server 上,并开起监听,等待 HTTP 请求
//     addr:监听地址
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
}

注释分析

  • Engine
    • 即之前看过的结构体
    • 从函数声明可以看出,Run 是 Engine 的一个函数方法
    • 调用时可以自定义监听地址(127.0.0.1、0.0.0.0 等)
    • 函数调用失败则返回错误
  • http.Server
    • Go 自带的 HTTP 服务库
    • 只提供一些比较底层的调用(使用不友好)
    • 推断出 Gin 基于 Go 自带的 http 服务库开发(实际上人家自己也说过)
  • 监听
    • Run 函数执行成功后处于监听状态
  • 等待 HTTP 请求
    • Run 执行成功后等待 HTTP 请求

代码初步分析

  • 第 4 行
    • 优秀的写法:函数结束时输出错误信息(如果有错误的话)
  • 第 6 行
    • 获取最终监听地址及端口
      • addr 长度为 0 时:获取环境变量 PORT(没环境变量则默认使用 8080 端口),返回 “:${PORT}”
      • addr 长度为 1 时:返回 addr[0]
      • 其余长度引发 panic
  • 第 7 行
    • 输出最终监听地址端口
  • 第 8 行(划重点,这里要考的 ☆☆☆☆☆)
    • 调用 Go 的 http.Server(什么时候有空再更深入的看看),设置 Handler 为 engine
      • Handler 是一个 interface
        • 有一个 ServeHTTP(ResponseWriter, *Request) 接口
      • Engine 实现了 ServeHTTP 接口
    • 监听并提供服务
      • 在 http.Server 接收到请求经过一系列处理后,会最终调用设置为 Handler 的 Engine 的 ServerHTTP 接口,并传递请求包(http.Request)
      • 至此,请求顺利到达 Gin 的 Engine(后续的处理看后续文章)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注