hhstore / blog

My Tech Blog: about Mojo / Rust / Golang / Python / Kotlin / Flutter / VueJS / Blockchain etc.
https://github.com/hhstore/blog/issues
291 stars 24 forks source link

Annotated: Golang Projects - Gin Web Framework #131

Open hhstore opened 5 years ago

hhstore commented 5 years ago

gin 源码剖析:

Related:

hhstore commented 5 years ago

gin:

gin 核心设计:

模块 实现方式 依赖
gin.Engine.pool 使用 sync.Pool 临时对象池 标准库 sync.Pool
gin.ServeHTTP() 使用 sync.pool 优化 标准库 net.http 接口实现
gin.Context 核心模块, 包含HTTP请求处理全过程 标准库
xxxx xxxxx xxxx
xxxx xxxxx xxxx
xxxx xxxxx xxxx

gin 关键代码文件:

context.go : 包含了 HTTP 请求上下文全部处理过程, requestresponse 两部分. gin 主体功能代码, 都在这里. 应该把 request 和 response 单独拆分开, 比较好. 虽然混在一起, 整体结构还是很清晰的.

hhstore commented 5 years ago

-

hhstore commented 5 years ago

gin v1.3.0 源码剖析:

0. 由官方示例开始:

  1. 找到第一个入口 gin.Default(), 由此展开整个gin框架.
  2. 第二关键处: r.Run(), 后续会逐步分析到.
  3. 通过这2个关键入口, 庖丁解牛, 把整个 gin 框架拆解开.
package main

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

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

1. gin.Default():

  1. 创建 gin 框架对象
  2. 配置 gin 默认中间件
  3. 返回 gin 框架对象
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()                                // 创建框架对象
    engine.Use(Logger(), Recovery())    // 配置默认中间件
    return engine                                    // 返回框架对象
}

2. gin.New()

  1. 初始化 Engine 对象, 关键步骤: 初始化 路由组
  2. 初始化 pool, 这是核心步骤. pool 用来存储 context 上下文对象. 用来优化处理 http 请求时的性能.
  3. 后续会重点分析 engine.pool.

func New() *Engine {
    debugPrintWARNINGNew()
    // 初始化框架对象
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        AppEngine:              defaultAppEngine,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        ....
    }

    engine.RouterGroup.engine = engine

    // 关键代码: 初始化 pool
    engine.pool.New = func() interface{} {
        return engine.allocateContext()   // 关键调用: 初始化上下文对象
    }
    return engine
}
hhstore commented 5 years ago

3. gin.Engine{ }:

  1. 暂时不细展开.

type Engine struct {
    RouterGroup  // 关键: 路由组

    // 几个配置开关:
    RedirectTrailingSlash  bool
    RedirectFixedPath      bool
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool

    AppEngine bool
    UseRawPath bool

    UnescapePathValues bool

    MaxMultipartMemory int64

    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap

    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain

    pool             sync.Pool     // 关键: 临时对象池: 用于处理 context 
    trees            methodTrees
}
hhstore commented 5 years ago

4. gin.Run()

// 启动框架, 处理 http 请求
func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    address := resolveAddress(addr)  // 获取 IP+Port
    debugPrint("Listening and serving HTTP on %s\n", address)

    err = http.ListenAndServe(address, engine) // 处理 HTTP 请求, 关键代码: 注意 engine 传参
    return
}
  1. 解析 IP + Port
  2. 使用 标准库 http.ListenAndServe() 启动 web 监听服务, 处理HTTP请求.
  3. 这里面, 需要特别注意传入了 engine 对象.

4.1 http.ListenAndServe()

// 注意 handler 的类型是 接口
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

4.2 http.Handler{ } 接口:

//  go/1.12/libexec/src/net/http/server.go

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
  1. Handler {} 是个接口类型
  2. Handler 内部只有一个 ServeHTTP() 方法声明. 这是关键点,
  3. gin 自己实现了 gin.ServeHTTP() 方法, 使 Engine 符合该接口声明.
hhstore commented 5 years ago

5. gin.ServeHTTP()


// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)  // 从 临时对象池 pool 获取 context 上下文对象
    c.writermem.reset(w)
    c.Request = req
    c.reset()

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

    engine.pool.Put(c)             // 使用完 context 对象, 归还给 pool 
}
  1. 源码注释, 清楚说明是 http.Handler 接口实现.
  2. engine.pool.Get().(*Context): 从临时对象池 取出一个 context 上下文对象
  3. engine.handleHTTPRequest(c): 处理 HTTP 请求
  4. engine.pool.Put(c): 处理完HTTP请求, 归还 context 对象到 pool
hhstore commented 5 years ago

6. gin.handleHTTPRequest()


func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    path := c.Request.URL.Path

    ...

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        ...

        root := t[i].root
        // Find route in tree
        handlers, params, tsr := root.getValue(path, c.Params, unescape)
        if handlers != nil {
            c.handlers = handlers
            c.Params = params

            // 关键调用 ! ! ! 
            c.Next()   // 具体执行处理

            c.writermem.WriteHeaderNow()
            return
        }

        if httpMethod != "CONNECT" && path != "/" {
            ....
        }
        break
    }

    // 后续是出错返回, 省略之...
    ....
}
  1. 省略一下非关键处理代码
  2. c.Next() 是最核心的代码: c是 Context 对象. 引出 Context 的实现细节.
hhstore commented 5 years ago

7. gin.Context.Next()

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
    c.index++

    // 注意: 逐个遍历, 执行 handler()
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)  // 注意传参 (c), 执行具体handler()方法
    }
}
  1. 注意原注释说明: Next() 只允许在 内部 中间件使用, 不可滥用.
  2. for 循环, 遍历 handlers 是个 数组: 内部存 方法集合.

7.1 gin.Context


// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
    writermem responseWriter

    Request   *http.Request    // HTTP 请求
    Writer    ResponseWriter   // HTTP 响应

    Params   Params
    handlers HandlersChain  // 关键: 数组: 内包含方法集合
    index    int8

    engine *Engine  // 关键: 引擎

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string
}
  1. 关键:

    Request http.Request : HTTP 请求 Writer ResponseWriter : HTTP 响应 handlers HandlersChain : 是方法集 engine Engine : gin框架对象

// 方法类型:
type HandlerFunc func(*Context)

// 数组: 方法集
type HandlersChain []HandlerFunc

HandlersChain 类型: 方法集合

hhstore commented 5 years ago

8. gin.GET() / gin.POST()

package main

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

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

r.GET("/ping", func(c *gin.Context){}): 这里可见: 定义 HTTP 请求路由. 进而找到 gin.GET()/ gin.POST() 等方法实现.

// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("POST", relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}
  1. gin.POST() 定义 POST 请求路由
  2. gin.GET() 定义 GET 请求路由
  3. 注意: group *RouterGroup
  4. 注意: 返回值类型: IRoutes

8.1 gin.RouterGroup{ }

// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
// and an array of handlers (middleware).
type RouterGroup struct {
    Handlers HandlersChain    // 数组: 路由集

    basePath string
    engine   *Engine     // gin 引擎
    root     bool
}

注意: 原注释提示. 配置路由.

8.2 gin.IRoutes {}

type IRoutes interface {
    Use(...HandlerFunc) IRoutes

    Handle(string, string, ...HandlerFunc) IRoutes
    Any(string, ...HandlerFunc) IRoutes

    GET(string, ...HandlerFunc) IRoutes       // HTTP GET
    POST(string, ...HandlerFunc) IRoutes    // HTTP POST

    DELETE(string, ...HandlerFunc) IRoutes
    PATCH(string, ...HandlerFunc) IRoutes
    PUT(string, ...HandlerFunc) IRoutes
    OPTIONS(string, ...HandlerFunc) IRoutes
    HEAD(string, ...HandlerFunc) IRoutes

    StaticFile(string, string) IRoutes
    Static(string, string) IRoutes
    StaticFS(string, http.FileSystem) IRoutes
}

8.3 gin.handle()

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()
}
  1. 添加路由