Gin 框架源码大体学习

2023-10-27

Gin 服务框架服务端使用示例:

package main

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

func main(){
    // 建立http路由
    router := gin.Default()
    router.GET("/gin/test/", func(context *gin.Context) {
        context.JSON(200,gin.H{
            "msg":"success",
        })
    })
    router.Run(":6789")
}

建立Gin路由

返回一个“引擎“,定义其日志组件和panic处理组件

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    engine := New()
    // 默认只开起日志和异常恢复的中间件
    engine.Use(Logger(), Recovery())
    return engine
}

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {
    engine := &Engine{
        // 定义根路径的handler
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        // 定义模版 map[string]interface{}
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    // sync.Pool
    engine.pool.New = func() interface{} {
        // 分配新context,实际上就是缓冲区
        return engine.allocateContext()
    }
    return engine
}

router 建立路由


// 定义所有路由
type methodTrees []methodTree
type methodTree struct {
    method string
    root   *node
}

type Engine struct {
    RouterGroup
    ... 
    trees            methodTrees
    pool             sync.Pool // gin context 缓存池,可reuse
    ...
}

type RouterGroup struct {
    // 中间件数组
    Handlers HandlersChain
    basePath string
    engine   *Engine
    root     bool
}

// 注册中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

// 注册访问路由
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
    group.handle("GET", relativePath, handlers)
    group.handle("POST", relativePath, handlers)
    group.handle("PUT", relativePath, handlers)
    group.handle("PATCH", relativePath, handlers)
    group.handle("HEAD", relativePath, handlers)
    group.handle("OPTIONS", relativePath, handlers)
    group.handle("DELETE", relativePath, handlers)
    group.handle("CONNECT", relativePath, handlers)
    group.handle("TRACE", relativePath, handlers)
    return group.returnObj()
}

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

// 把新路由加入树中,每个方法是一个树的根节点
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})
    }
    // 大体形成类似于字典树[路径=>handlers]
    // 具体实现逻辑可参考 github.com/gin-gonic/gin/tree.go,httprouter相关参考:http://www.okyes.me/2016/05/08/httprouter.html
    root.addRoute(path, handlers)
}

Run Gin Server

// 相当于go net/http 封装
func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()
    address := resolveAddress(addr)
    // gin engine 实现了net/http的Handler,相当于gin自己做路由分发
    err = http.ListenAndServe(address, engine)
    return
}

// net/http 监听端口
func ListenAndServe(addr string, handler Handler) error {
    // new server
    server := &Server{Addr: addr, Handler: handler}
    // listen the port
    return server.ListenAndServe() // 底层逻辑调用Server函数,监听端口+接收可读事件+goroutine处理请求
}

// 监听端口+接收可读事件+goroutine处理请求
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()

    // 没有事件到达时,sleep多久
    var tempDelay time.Duration // how long to sleep on accept failure

    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    // 主goroutine
    baseCtx := context.Background() // base is always background, per Issue 16220
    // 绑定参数
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())

    // accept request
    for {
        // 端口是否可读
        rw, e := l.Accept()
        if e != nil { //不可读或者端口异常、服务异常
            select {
            case <-srv.getDoneChan(): // 如果 http 服务关闭,直接退出程序
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 { // 如果tempDelay == 0 
                    tempDelay = 5 * time.Millisecond // 则 tempDelay == 5ms
                } else {
                    tempDelay *= 2 // 否则 tempDelay * 2
                }
                // tempDelay限定最大1s
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        // 可读=>tempDelay sleep 时间归0
        tempDelay = 0
        // 读取http数据
        c := srv.newConn(rw)
        // 标示请求为新建状态StateNew,除此之外还有:StateActive、StateIdle、StateHijacked、StateClosed
        c.setState(c.rwc, StateNew) // before Serve can return
        // goroutine处理子context
        go c.serve(ctx)
    }
}
// conn中有
func (srv *Server) newConn(rwc net.Conn) *conn {
    c := &conn{
        server: srv,
        rwc:    rwc, //conn,涉及文件描述符操作:可参考 net/fd_unix.go
    }
    return c
}

handler conn

// 接上,子context绑定了一些参数,同时在子context中获取http请求相关信息,例如ip、port、urlpath、body等
func (c *conn) serve(ctx context.Context) {
    // get remote addr
    c.remoteAddr = c.rwc.RemoteAddr().String()

    ... // 处理异常

    // 如果是https请求
    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        // ... 设置读超时
        // ... 设置写超时
        // ssl/tls握手失败,参考链接:http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
        c.tlsState = new(tls.ConnectionState)
        *c.tlsState = tlsConn.ConnectionState()
        ... // 没理解这段校验代码,跳过
    }

    // HTTP/1.x from here on.

    // 派生可cancel的context,即父context若完成或超时,子context也会cancel,防止资源泄漏
    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    // 获取reader和writer
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        // 组装response对象,其中包括request对象
        w, err := c.readRequest(ctx)
        if c.r.remain != c.server.initialReadLimitSize() {
            // 修改当前context状态为活动中
            c.setState(c.rwc, StateActive)
        }
        if err != nil {
            ...
            // 组装过程出现问题,输出一些内容,然后直接退出
            return
        }

        // 对req进行校验,以及包装其body,方便代码复用获取body
        // Expect 100 Continue support
        req := w.req
        if req.expectsContinue() {
            if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
                // Wrap the Body reader with one that replies on the connection
                req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
            }
        } else if req.Header.get("Expect") != "" {
            ... // 这个校验没理解
            return
        }

        // curReq atomic.Value
        // 线程安全,conn关联response
        c.curReq.Store(w)

        // 异步读取body,通过sync.Cond 阻塞,直到完成读取唤醒阻塞
        if requestBodyRemains(req.Body) {
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            if w.conn.bufr.Buffered() > 0 {
                w.conn.r.closeNotifyFromPipelinedRequest()
            }
            w.conn.r.startBackgroundRead()
        }

        // 处理http
        serverHandler{c.server}.ServeHTTP(w, w.req)

        // 结束请求
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()

        // 是否reuse
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
        c.curReq.Store((*response)(nil))

        if !w.conn.server.doKeepAlives() {
            // We're in shutdown mode. We might've replied
            // to the user without "Connection: close" and
            // they might think they can send another
            // request, but such is life with HTTP/1.1.
            return
        }

        if d := c.server.idleTimeout(); d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
            if _, err := c.bufr.Peek(4); err != nil {
                return
            }
        }
        c.rwc.SetReadDeadline(time.Time{})
    }
}

// goroutine 读取
func (cr *connReader) startBackgroundRead() {
    cr.lock()
    defer cr.unlock()
    ...
    cr.inRead = true
    cr.conn.rwc.SetReadDeadline(time.Time{})
    go cr.backgroundRead()
}

// sync.Cond 逻辑处理
func (cr *connReader) backgroundRead() {
    n, err := cr.conn.rwc.Read(cr.byteBuf[:])
    cr.lock()
    if n == 1 {
        cr.hasByte = true
        // We were at EOF already (since we wouldn't be in a
        // background read otherwise), so this is a pipelined
        // HTTP request.
        cr.closeNotifyFromPipelinedRequest()
    }
    if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
        // Ignore this error. It's the expected error from
        // another goroutine calling abortPendingRead.
    } else if err != nil {
        cr.handleReadError(err)
    }
    cr.aborted = false
    cr.inRead = false
    cr.unlock()
    // 唤醒阻塞
    cr.cond.Broadcast()
}

处理http请求

// 是个interface
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// gin 实现了这个接口,net/http调用跳至此
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context) //reuse
    c.writermem.reset(w)
    c.Request = req
    c.reset() //reset

    engine.handleHTTPRequest(c)
    engine.pool.Put(c) //reuse
}

// 大体逻辑
func (engine *Engine) handleHTTPRequest(context *Context) {
    httpMethod := context.Request.Method
    path := context.Request.URL.Path
    unescape := false
    if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
        path = context.Request.URL.RawPath
        unescape = engine.UnescapePathValues
    }

    // 根据urlpath从字典树结构取出相对应的处理handler数组
    // 忽略寻找路由相关逻辑
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        if t[i].method == httpMethod {
            root := t[i].root
            // 找到对于的handler数组
            handlers, params, tsr := root.getValue(path, context.Params, unescape)
            if handlers != nil {
                context.handlers = handlers
                context.Params = params
                context.Next()
                // 写resp header
                context.writermem.WriteHeaderNow()
                return
            }
            ...
        }
    }

    ...
    // 404
}

// next遍历handler方法数组,然后调用
func (c *Context) Next() {
    c.index++
    s := int8(len(c.handlers))
    for ; c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

// gin context 定义,并非context
type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain // handler 数组
    index    int8 // 当前handler index

    engine *Engine

    // c.Set
    Keys map[string]interface{}

    ...
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Gin 框架源码大体学习 的相关文章

  • go struct{} 空结构体的特点和作用

    空结构体的特点和作用 参考代码 package main import fmt unsafe func main empStruct 空结构体的实例和作用 func empStruct 空结构体的特点 1 不占用内存 2 地址不变 var
  • Go语言实现区块链与加密货币-Part3(交易优化,单机模拟多节点通信)

    交易 二 在这个系列文章的一开始 我们就提到了 区块链是一个分布式数据库 不过在之前的文章中 我们选择性地跳过了 分布式 这个部分 而是将注意力都放到了 数据库 部分 到目前为止 我们几乎已经实现了一个区块链数据库的所有元素 今天 我们将会
  • Golang-使用 goroutine 运行闭包的“坑”

    介绍 在 Go 语言中 函数支持匿名函数 闭包就是一种特殊的匿名函数 它可以用于访问函数体外部的变量 需要注意的是 在 for range 中 使用 goroutine 执行闭包时 经常会掉 坑 因为匿名函数可以访问函数体外部的变量 而 f
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • golang sleep

    golang的休眠可以使用time包中的sleep 函数原型为 func Sleep d Duration 其中的Duration定义为 type Duration int64 Duration的单位为 nanosecond 为了便于使用
  • go语言基础-----03-----流程控制、函数、值传递、引用传递、defer函数

    1 流程控制 这里只讲 for range 语句 这个关键字 主要用于遍历 用来遍历数组 slice map chan 例如 package main import fmt func main str hello world 中国 for
  • Go切片排序

    Go 语言标准库提供了sort包 用于对切片和用户定义的集合进行排序 具体示例如下 基本排序 package main import fmt sort func main float 从小到大排序 f float64 5 2 1 3 0 7
  • Golang连接Jenkins获取Job Build状态及相关信息

    文章目录 1 连接Jenkins 2 controller 3 module 4 router 5 效果展示 第三方包 gojenkins 方法文档 gojenkins docs 实现起来很简单 利用第三方库 连接jenkins 调用相关方
  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • Golang 内存对齐视频

    https www bilibili com video BV1Ja4y1i7AF 简而言之 就是注意写代码的时候要把相同类型的元素放在一起 更进一步需要自己将结构体配对为32位或64位的整数倍 有助于减少额外空间消耗
  • Go 程序编译过程(基于 Go1.21)

    版本说明 Go 1 21 官方文档 Go 语言官方文档详细阐述了 Go 语言编译器的具体执行过程 Go1 21 版本可以看这个 https github com golang go tree release branch go1 21 sr
  • 有哪些不错的 Golang 开源项目?

    目前人在字节做 Go 开发 寻找 Golang 开源项目学习目的可能是 想学习或者提高自己对 Go 项目的组织和编排能力 想学习 Go 项目的框架设计 想在一些 Go 语法上细节的优化和进阶 我推荐两个项目 一 tinode 这是一个开源的
  • go-zero 开发入门-加法客服端示例

    定义 RPC 接口文件 接口文件 add proto 的内容如下 syntax proto3 package add 当 protoc gen go 版本大于 1 4 0 时需加上 go package 否则编译报错 unable to d
  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • go语言实现文件夹上传前后端代码案例

    go语言实现文件夹上传前后端代码案例 前端用于上传的测试界面 如果上传的文件夹有子文件要遍历子文件夹创建出子文件夹再进行拷贝 需要获取文件名和对应的路径 将文件的相对路径和文件对象添加到FormData中 这几行代码很关键 for let
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • [每周一更]-(第55期):Go的interface

    参考地址 https juejin cn post 6978322067775029261 https gobyexample com interfaces https go dev tour methods 9 介绍下Go的interfa
  • golang 生成一年的周数

    GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB T 7408 2005 参数 year 年份 GB T 7408 2005 func GetWeekTimeCycleForGBT74
  • go-carbon v2.3.4 发布,轻量级、语义化、对开发者友好的 Golang 时间处理库

    carbon 是一个轻量级 语义化 对开发者友好的 golang 时间处理库 支持链式调用 目前已被 awesome go 收录 如果您觉得不错 请给个 star 吧 github com golang module carbon gite
  • 【go语言】读取toml文件

    一 简介 TOML 全称为Tom s Obvious Minimal Language 是一种易读的配置文件格式 旨在成为一个极简的数据序列化语言 TOML的设计原则之一是保持简洁性 易读性 同时提供足够的灵活性以满足各种应用场景 TOML

随机推荐