欢乐狗 发表于 2024-11-5 17:25:27

go 语言 Gin Web 框架的实现原理探究

Gin 是一个用 Go (Golang) 编写的 Web 框架,性能极优,具有快速、支持中心件、crash处理、json验证、路由组、错误管理、内存渲染、可扩展性等特点。
官网地址:https://gin-gonic.com/
源码地址:https://github.com/gin-gonic/gin/tree/v1.10.0
参考视频:gin框架底层技能原理分析_哔哩哔哩_bilibili

一、net/http 及 gin 使用示例



[*]使用 net/http 创建 web 服务
package main

import (
        "net/http"
)

func main() {
        // 使用 net/http 创建 web 服务
        // 注册路由
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Hello World!"))
        })

        // 启动监听
        http.ListenAndServe(":8080", nil)
}

[*]使用 gin 创建 web 服务
package main

import (
        "net/http"

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

func main() {
        // 初始化 engine
        r := gin.Default()

        // 注册路由
        r.GET("/ping", func(c *gin.Context) {
                c.JSON(http.StatusOK, gin.H{
                        "message": "pong",
                })
        })

        // 启动服务
        r.Run()
} 二、gin.Engine数据结构

type Engine struct {
        RouterGroup// 路由组

        RedirectTrailingSlash bool

        RedirectFixedPath bool

        HandleMethodNotAllowed bool

        ForwardedByClientIP bool

        AppEngine bool

        UseRawPath bool

        UnescapePathValues bool

        RemoveExtraSlash bool

        RemoteIPHeaders []string

        TrustedPlatform string

        MaxMultipartMemory int64

        // UseH2C enable h2c support.
        UseH2C bool

        ContextWithFallback bool

        delims         render.Delims
        secureJSONPrefix string
        HTMLRender       render.HTMLRender
        FuncMap          template.FuncMap
        allNoRoute       HandlersChain
        allNoMethod      HandlersChain
        noRoute          HandlersChain
        noMethod         HandlersChain

    // 对象池,用来复用gin.Context
        pool             sync.Pool

    // 路由树,根据不同的httpmethod,以及不同的路由,拆分http请求及处理函数
        trees            methodTrees   
        maxParams      uint16
        maxSections      uint16
        trustedProxies   []string
        trustedCIDRs   []*net.IPNet
}

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

type Pool struct {
        noCopy noCopy

        local   unsafe.Pointer // local fixed-size per-P pool, actual type is poolLocal
        localSize uintptr      // size of the local array

        victim   unsafe.Pointer // local from previous cycle
        victimSize uintptr      // size of victims array

        // New optionally specifies a function to generate
        // a value when Get would otherwise return nil.
        // It may not be changed concurrently with calls to Get.
        New func() any
}

type methodTree struct {
        method string
        root   *node
}

type methodTrees []methodTree

type node struct {
        path      string
        indices   string
        wildChild bool
        nType   nodeType
        priorityuint32
        children[]*node // child nodes, at most 1 :param style node at the end of the array
        handlersHandlersChain
        fullPathstring
}

type HandlersChain []HandlerFunc // src/net/http/method.go

// http method
const (
        MethodGet   = "GET"
        MethodHead    = "HEAD"
        MethodPost    = "POST"
        MethodPut   = "PUT"
        MethodPatch   = "PATCH" // RFC 5789
        MethodDelete= "DELETE"
        MethodConnect = "CONNECT"
        MethodOptions = "OPTIONS"
        MethodTrace   = "TRACE"
)
三、gin 注册路由流程



[*]创建engine
func Default(opts ...OptionFunc) *Engine {
        debugPrintWARNINGDefault()
        engine := New()
        engine.Use(Logger(), Recovery())
        return engine.With(opts...)
}

func New(opts ...OptionFunc) *Engine {
        debugPrintWARNINGNew()
        engine := &Engine{
                RouterGroup: RouterGroup{
                        Handlers: nil,
                        basePath: "/",
                        root:   true,
                },
                FuncMap:                template.FuncMap{},
                RedirectTrailingSlash:true,
                RedirectFixedPath:      false,
                HandleMethodNotAllowed: false,
                ForwardedByClientIP:    true,
                RemoteIPHeaders:      []string{"X-Forwarded-For", "X-Real-IP"},
                TrustedPlatform:      defaultPlatform,
                UseRawPath:             false,
                RemoveExtraSlash:       false,
                UnescapePathValues:   true,
                MaxMultipartMemory:   defaultMultipartMemory,
                // 9 棵路由压缩前缀树,对应 http 的 9 种方法
      trees:                  make(methodTrees, 0, 9),
                delims:               render.Delims{Left: "{{", Right: "}}"},
                secureJSONPrefix:       "while(1);",
                trustedProxies:         []string{"0.0.0.0/0", "::/0"},
                trustedCIDRs:         defaultTrustedCIDRs,
        }
        engine.RouterGroup.engine = engine
        engine.pool.New = func() any {
                return engine.allocateContext(engine.maxParams)
        }
        return engine.With(opts...)
}

[*]注册MiddleWare
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
        engine.RouterGroup.Use(middleware...)
        engine.rebuild404Handlers()
        engine.rebuild405Handlers()
        return engine
}

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

[*]注册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()
}

func joinPaths(absolutePath, relativePath string) string {
        if relativePath == "" {
                return absolutePath
        }

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

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, handlers)
        return mergedHandlers
}

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
        assert1(path == '/', "path must begin with '/'")
        assert1(method != "", "HTTP method can not be empty")
        assert1(len(handlers) > 0, "there must be at least one handler")

        debugPrintRoute(method, path, handlers)

        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)

        if paramsCount := countParams(path); paramsCount > engine.maxParams {
                engine.maxParams = paramsCount
        }

        if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
                engine.maxSections = sectionsCount
        }
}

func (n *node) addRoute(path string, handlers HandlersChain) {
        fullPath := path
        n.priority++

        // Empty tree
        if len(n.path) == 0 && len(n.children) == 0 {
                n.insertChild(path, fullPath, handlers)
                n.nType = root
                return
        }

        parentFullPathIndex := 0

walk:
        for {
                // Find the longest common prefix.
                // This also implies that the common prefix contains no ':' or '*'
                // since the existing key can't contain those chars.
                i := longestCommonPrefix(path, n.path)

                // Split edge
                if i < len(n.path) {
                        child := node{
                                path:      n.path,
                                wildChild: n.wildChild,
                                nType:   static,
                                indices:   n.indices,
                                children:n.children,
                                handlers:n.handlers,
                                priority:n.priority - 1,
                                fullPath:n.fullPath,
                        }

                        n.children = []*node{&child}
                        // []byte for proper unicode char conversion, see #65
                        n.indices = bytesconv.BytesToString([]byte{n.path})
                        n.path = path[:i]
                        n.handlers = nil
                        n.wildChild = false
                        n.fullPath = fullPath[:parentFullPathIndex+i]
                }

                // Make new node a child of this node
                if i < len(path) {
                        path = path
                        c := path

                        // '/' after param
                        if n.nType == param && c == '/' && len(n.children) == 1 {
                                parentFullPathIndex += len(n.path)
                                n = n.children
                                n.priority++
                                continue walk
                        }

                        // Check if a child with the next path byte exists
                        for i, max := 0, len(n.indices); i < max; i++ {
                                if c == n.indices {
                                        parentFullPathIndex += len(n.path)
                                        i = n.incrementChildPrio(i)
                                        n = n.children
                                        continue walk
                                }
                        }

                        // Otherwise insert it
                        if c != ':' && c != '*' && n.nType != catchAll {
                                // []byte for proper unicode char conversion, see #65
                                n.indices += bytesconv.BytesToString([]byte{c})
                                child := &node{
                                        fullPath: fullPath,
                                }
                                n.addChild(child)
                                n.incrementChildPrio(len(n.indices) - 1)
                                n = child
                        } else if n.wildChild {
                                // inserting a wildcard node, need to check if it conflicts with the existing wildcard
                                n = n.children
                                n.priority++

                                // Check if the wildcard matches
                                if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
                                        // Adding a child to a catchAll is not possible
                                        n.nType != catchAll &&
                                        // Check for longer wildcard, e.g. :name and :names
                                        (len(n.path) >= len(path) || path == '/') {
                                        continue walk
                                }

                                // Wildcard conflict
                                pathSeg := path
                                if n.nType != catchAll {
                                        pathSeg = strings.SplitN(pathSeg, "/", 2)
                                }
                                prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
                                panic("'" + pathSeg +
                                        "' in new path '" + fullPath +
                                        "' conflicts with existing wildcard '" + n.path +
                                        "' in existing prefix '" + prefix +
                                        "'")
                        }

                        n.insertChild(path, fullPath, handlers)
                        return
                }

                // Otherwise add handle to current node
                if n.handlers != nil {
                        panic("handlers are already registered for path '" + fullPath + "'")
                }
                n.handlers = handlers
                n.fullPath = fullPath
                return
        }
}
四、gin 服务启动流程

   调用 *Engine.Run() 方法,底层会把 gin.Engine 本身作为 net/http 包下的 handler 接口的实现类,并调用 http.ListenAndServe 方法启动服务。
func (engine *Engine) Run(addr ...string) (err error) {
        defer func() { debugPrintError(err) }()

        if engine.isUnsafeTrustedProxies() {
                debugPrint(" 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
}

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

func (engine *Engine) handleHTTPRequest(c *Context) {
        httpMethod := c.Request.Method
        rPath := c.Request.URL.Path
        unescape := false
        if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
                rPath = c.Request.URL.RawPath
                unescape = engine.UnescapePathValues
        }

        if engine.RemoveExtraSlash {
                rPath = cleanPath(rPath)
        }

        // Find root of the tree for the given HTTP method
        t := engine.trees
        for i, tl := 0, len(t); i < tl; i++ {
                if t.method != httpMethod {
                        continue
                }
                root := t.root
                // Find route in tree
                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
                }
                if httpMethod != http.MethodConnect && rPath != "/" {
                        if value.tsr && engine.RedirectTrailingSlash {
                                redirectTrailingSlash(c)
                                return
                        }
                        if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                                return
                        }
                }
                break
        }

        if engine.HandleMethodNotAllowed {
                // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
                // containing a list of the target resource's currently supported methods.
                allowed := make([]string, 0, len(t)-1)
                for _, tree := range engine.trees {
                        if tree.method == httpMethod {
                                continue
                        }
                        if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
                                allowed = append(allowed, tree.method)
                        }
                }
                if len(allowed) > 0 {
                        c.handlers = engine.allNoMethod
                        c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
                        serveError(c, http.StatusMethodNotAllowed, default405Body)
                        return
                }
        }

        c.handlers = engine.allNoRoute
        serveError(c, http.StatusNotFound, default404Body)
}   Engine.handleHTTPRequest 处理 http 哀求的流程:


[*]根据 http method 取得对应的 methodTree
[*]根据 path 从 methodTree 中取得对应的 handlers 链
[*]将 handlers 链注入到 gin.context 中, 通过 context.Next() 方法遍历获取 handler


五、路由树原理

   1、前缀树
又称Trie树、字典树或键树,是一种树形数据结构,主要用于高效地存储和查询字符串。
其特点为:


[*]根节点不包含字符,除根节点外每一个节点都只包含一个字符。
[*]从根节点到某一节点,路径上颠末的字符连接起来,为该节点对应的字符串。
[*]每个节点的所有子节点包含的字符都不相同。
https://i-blog.csdnimg.cn/direct/c2826470fef648449126a3d7db1d834f.png

2、压缩前缀树
压缩前缀树是一种高效处理字符串聚集的数据结构,通过归并共享的前缀来节省存储空间并提高检索效率。它在网络路由、编译器符号表和字符串检索等领域有广泛应用。
https://i-blog.csdnimg.cn/direct/1e7e2ad6b760491b9ef493985a6fbff5.png

3、路由树的数据结构
gin路由管理采取压缩前缀树的数据结构,相较于map数据结构,压缩前缀树可用于暗昧匹配,并且在数据量不大的情况下,压缩树的性能并不比map差。gin框架中采取补偿策略,将挂载的路径越多的子节点越往左排列,以便查询时优先被访问到。
type methodTree struct {
        method string
        root   *node
}

type methodTrees []methodTree

type node struct {
        path      string
    // 每个 indice 字符对应一个孩子节点路径的首字母
        indices   string
        wildChild bool
        nType   nodeType
    // 后续节点数量
        priorityuint32
    // 孩子节点列表
        children[]*node // child nodes, at most 1 :param style node at the end of the array
    // 处理函数链
        handlersHandlersChain
        fullPathstring
}

type HandlersChain []HandlerFunc4、注册路由
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
        assert1(path == '/', "path must begin with '/'")
        assert1(method != "", "HTTP method can not be empty")
        assert1(len(handlers) > 0, "there must be at least one handler")

        debugPrintRoute(method, path, handlers)

        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)

        if paramsCount := countParams(path); paramsCount > engine.maxParams {
                engine.maxParams = paramsCount
        }

        if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
                engine.maxSections = sectionsCount
        }
}

func (n *node) addRoute(path string, handlers HandlersChain) {
        fullPath := path
        n.priority++

        // Empty tree
        if len(n.path) == 0 && len(n.children) == 0 {
                n.insertChild(path, fullPath, handlers)
                n.nType = root
                return
        }

        parentFullPathIndex := 0

walk:
        for {
                // Find the longest common prefix.
                // This also implies that the common prefix contains no ':' or '*'
                // since the existing key can't contain those chars.
                i := longestCommonPrefix(path, n.path)

                // Split edge
                if i < len(n.path) {
                        child := node{
                                path:      n.path,
                                wildChild: n.wildChild,
                                nType:   static,
                                indices:   n.indices,
                                children:n.children,
                                handlers:n.handlers,
                                priority:n.priority - 1,
                                fullPath:n.fullPath,
                        }

                        n.children = []*node{&child}
                        // []byte for proper unicode char conversion, see #65
                        n.indices = bytesconv.BytesToString([]byte{n.path})
                        n.path = path[:i]
                        n.handlers = nil
                        n.wildChild = false
                        n.fullPath = fullPath[:parentFullPathIndex+i]
                }

                // Make new node a child of this node
                if i < len(path) {
                        path = path
                        c := path

                        // '/' after param
                        if n.nType == param && c == '/' && len(n.children) == 1 {
                                parentFullPathIndex += len(n.path)
                                n = n.children
                                n.priority++
                                continue walk
                        }

                        // Check if a child with the next path byte exists
                        for i, max := 0, len(n.indices); i < max; i++ {
                                if c == n.indices {
                                        parentFullPathIndex += len(n.path)
                                        i = n.incrementChildPrio(i)
                                        n = n.children
                                        continue walk
                                }
                        }

                        // Otherwise insert it
                        if c != ':' && c != '*' && n.nType != catchAll {
                                // []byte for proper unicode char conversion, see #65
                                n.indices += bytesconv.BytesToString([]byte{c})
                                child := &node{
                                        fullPath: fullPath,
                                }
                                n.addChild(child)
                                n.incrementChildPrio(len(n.indices) - 1)
                                n = child
                        } else if n.wildChild {
                                // inserting a wildcard node, need to check if it conflicts with the existing wildcard
                                n = n.children
                                n.priority++

                                // Check if the wildcard matches
                                if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
                                        // Adding a child to a catchAll is not possible
                                        n.nType != catchAll &&
                                        // Check for longer wildcard, e.g. :name and :names
                                        (len(n.path) >= len(path) || path == '/') {
                                        continue walk
                                }

                                // Wildcard conflict
                                pathSeg := path
                                if n.nType != catchAll {
                                        pathSeg = strings.SplitN(pathSeg, "/", 2)
                                }
                                prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
                                panic("'" + pathSeg +
                                        "' in new path '" + fullPath +
                                        "' conflicts with existing wildcard '" + n.path +
                                        "' in existing prefix '" + prefix +
                                        "'")
                        }

                        n.insertChild(path, fullPath, handlers)
                        return
                }

                // Otherwise add handle to current node
                if n.handlers != nil {
                        panic("handlers are already registered for path '" + fullPath + "'")
                }
                n.handlers = handlers
                n.fullPath = fullPath
                return
        }
}
5、检索路由
type nodeValue struct {
        handlers HandlersChain
        params   *Params
        tsr      bool
        fullPath string
}

type skippedNode struct {
        path      string
        node      *node
        paramsCount int16
}

func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
        var globalParamsCount int16

walk: // Outer loop for walking the tree
        for {
                prefix := n.path
                if len(path) > len(prefix) {
                        if path[:len(prefix)] == prefix {
                                path = path

                                // Try all the non-wildcard children first by matching the indices
                                idxc := path
                                for i, c := range []byte(n.indices) {
                                        if c == idxc {
                                                //strings.HasPrefix(n.children.path, ":") == n.wildChild
                                                if n.wildChild {
                                                        index := len(*skippedNodes)
                                                        *skippedNodes = (*skippedNodes)[:index+1]
                                                        (*skippedNodes) = skippedNode{
                                                                path: prefix + path,
                                                                node: &node{
                                                                        path:      n.path,
                                                                        wildChild: n.wildChild,
                                                                        nType:   n.nType,
                                                                        priority:n.priority,
                                                                        children:n.children,
                                                                        handlers:n.handlers,
                                                                        fullPath:n.fullPath,
                                                                },
                                                                paramsCount: globalParamsCount,
                                                        }
                                                }

                                                n = n.children
                                                continue walk
                                        }
                                }

                                if !n.wildChild {
                                        // If the path at the end of the loop is not equal to '/' and the current node has no child nodes
                                        // the current node needs to roll back to last valid skippedNode
                                        if path != "/" {
                                                for length := len(*skippedNodes); length > 0; length-- {
                                                        skippedNode := (*skippedNodes)
                                                        *skippedNodes = (*skippedNodes)[:length-1]
                                                        if strings.HasSuffix(skippedNode.path, path) {
                                                                path = skippedNode.path
                                                                n = skippedNode.node
                                                                if value.params != nil {
                                                                        *value.params = (*value.params)[:skippedNode.paramsCount]
                                                                }
                                                                globalParamsCount = skippedNode.paramsCount
                                                                continue walk
                                                        }
                                                }
                                        }

                                        // Nothing found.
                                        // We can recommend to redirect to the same URL without a
                                        // trailing slash if a leaf exists for that path.
                                        value.tsr = path == "/" && n.handlers != nil
                                        return value
                                }

                                // Handle wildcard child, which is always at the end of the array
                                n = n.children
                                globalParamsCount++

                                switch n.nType {
                                case param:
                                        // fix truncate the parameter
                                        // tree_test.goline: 204

                                        // Find param end (either '/' or path end)
                                        end := 0
                                        for end < len(path) && path != '/' {
                                                end++
                                        }

                                        // Save param value
                                        if params != nil {
                                                // Preallocate capacity if necessary
                                                if cap(*params) < int(globalParamsCount) {
                                                        newParams := make(Params, len(*params), globalParamsCount)
                                                        copy(newParams, *params)
                                                        *params = newParams
                                                }

                                                if value.params == nil {
                                                        value.params = params
                                                }
                                                // Expand slice within preallocated capacity
                                                i := len(*value.params)
                                                *value.params = (*value.params)[:i+1]
                                                val := path[:end]
                                                if unescape {
                                                        if v, err := url.QueryUnescape(val); err == nil {
                                                                val = v
                                                        }
                                                }
                                                (*value.params) = Param{
                                                        Key:   n.path,
                                                        Value: val,
                                                }
                                        }

                                        // we need to go deeper!
                                        if end < len(path) {
                                                if len(n.children) > 0 {
                                                        path = path
                                                        n = n.children
                                                        continue walk
                                                }

                                                // ... but we can't
                                                value.tsr = len(path) == end+1
                                                return value
                                        }

                                        if value.handlers = n.handlers; value.handlers != nil {
                                                value.fullPath = n.fullPath
                                                return value
                                        }
                                        if len(n.children) == 1 {
                                                // No handle found. Check if a handle for this path + a
                                                // trailing slash exists for TSR recommendation
                                                n = n.children
                                                value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
                                        }
                                        return value

                                case catchAll:
                                        // Save param value
                                        if params != nil {
                                                // Preallocate capacity if necessary
                                                if cap(*params) < int(globalParamsCount) {
                                                        newParams := make(Params, len(*params), globalParamsCount)
                                                        copy(newParams, *params)
                                                        *params = newParams
                                                }

                                                if value.params == nil {
                                                        value.params = params
                                                }
                                                // Expand slice within preallocated capacity
                                                i := len(*value.params)
                                                *value.params = (*value.params)[:i+1]
                                                val := path
                                                if unescape {
                                                        if v, err := url.QueryUnescape(path); err == nil {
                                                                val = v
                                                        }
                                                }
                                                (*value.params) = Param{
                                                        Key:   n.path,
                                                        Value: val,
                                                }
                                        }

                                        value.handlers = n.handlers
                                        value.fullPath = n.fullPath
                                        return value

                                default:
                                        panic("invalid node type")
                                }
                        }
                }

                if path == prefix {
                        // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
                        // the current node needs to roll back to last valid skippedNode
                        if n.handlers == nil && path != "/" {
                                for length := len(*skippedNodes); length > 0; length-- {
                                        skippedNode := (*skippedNodes)
                                        *skippedNodes = (*skippedNodes)[:length-1]
                                        if strings.HasSuffix(skippedNode.path, path) {
                                                path = skippedNode.path
                                                n = skippedNode.node
                                                if value.params != nil {
                                                        *value.params = (*value.params)[:skippedNode.paramsCount]
                                                }
                                                globalParamsCount = skippedNode.paramsCount
                                                continue walk
                                        }
                                }
                                //        n = latestNode.children
                        }
                        // We should have reached the node containing the handle.
                        // Check if this node has a handle registered.
                        if value.handlers = n.handlers; value.handlers != nil {
                                value.fullPath = n.fullPath
                                return value
                        }

                        // If there is no handle for this route, but this route has a
                        // wildcard child, there must be a handle for this path with an
                        // additional trailing slash
                        if path == "/" && n.wildChild && n.nType != root {
                                value.tsr = true
                                return value
                        }

                        if path == "/" && n.nType == static {
                                value.tsr = true
                                return value
                        }

                        // No handle found. Check if a handle for this path + a
                        // trailing slash exists for trailing slash recommendation
                        for i, c := range []byte(n.indices) {
                                if c == '/' {
                                        n = n.children
                                        value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
                                                (n.nType == catchAll && n.children.handlers != nil)
                                        return value
                                }
                        }

                        return value
                }

                // Nothing found. We can recommend to redirect to the same URL with an
                // extra trailing slash if a leaf exists for that path
                value.tsr = path == "/" ||
                        (len(prefix) == len(path)+1 && prefix == '/' &&
                                path == prefix[:len(prefix)-1] && n.handlers != nil)

                // roll back to last valid skippedNode
                if !value.tsr && path != "/" {
                        for length := len(*skippedNodes); length > 0; length-- {
                                skippedNode := (*skippedNodes)
                                *skippedNodes = (*skippedNodes)[:length-1]
                                if strings.HasSuffix(skippedNode.path, path) {
                                        path = skippedNode.path
                                        n = skippedNode.node
                                        if value.params != nil {
                                                *value.params = (*value.params)[:skippedNode.paramsCount]
                                        }
                                        globalParamsCount = skippedNode.paramsCount
                                        continue walk
                                }
                        }
                }

                return value
        }
}
六、gin.Context数据结构

type Context struct {
        writermem responseWriter
        Request   *http.Request
        Writer    ResponseWriter

        Params   Params
        handlers HandlersChain
        index    int8
        fullPath string

        engine       *Engine
        params       *Params
        skippedNodes *[]skippedNode

        // This mutex protects Keys map.
        mu sync.RWMutex

        // Keys is a key/value pair exclusively for the context of each request.
        Keys mapany

        // 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

        // queryCache caches the query result from c.Request.URL.Query().
        queryCache url.Values

        // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
        // or PUT body parameters.
        formCache url.Values

        // SameSite allows a server to define a cookie attribute making it impossible for
        // the browser to send this cookie along with cross-site requests.
        sameSite http.SameSite
}   gin.Context 作为处理 http 哀求的通用数据结构,不可克制地会被频仍创建和销毁。为了缓解GC 压力,gin 中采取对象池sync.Pool进行Context 的缓存复用,处理流程如下:


[*]http 哀求到达时,从 pool 中获取 Context,倘若池子已空,通过 pool.New 方法构造新的 Context补上空缺
[*]http 哀求处理完成后,将Context 放回 pool 中,用以后续复用
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)
}  
七、总结

   

[*]gin 将 Engine 作为 http.Handler 的实现类进行注入,从而融入 Golang net/http 标准库的框架之内
[*]gin 中基于 handler 链的方式实现中心件和处理函数的协调使用
[*]gin 中基于压缩前缀树的方式作为路由树的数据结构,对应于9种 http 方法共有 9 棵树
[*]gin 中基于 gin.Context 作为一次 http 哀求贯穿整条 handler chain 的核心数据结构
[*]gin.Context 是一种会被频仍创建销毁的资源对象,因此使用对象池 sync.Pool 进行缓存复用


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: go 语言 Gin Web 框架的实现原理探究