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 使用示例
- 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)
- }
复制代码
- 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 [P]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
- priority uint32
- children []*node // child nodes, at most 1 :param style node at the end of the array
- handlers HandlersChain
- fullPath string
- }
- 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 注册路由流程
- 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...)
- }
复制代码
- 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()
- }
复制代码
以 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[len(group.Handlers):], handlers)
- return mergedHandlers
- }
- func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
- assert1(path[0] == '/', "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[i:],
- 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[i]})
- 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[i:]
- c := path[0]
- // '/' after param
- if n.nType == param && c == '/' && len(n.children) == 1 {
- parentFullPathIndex += len(n.path)
- n = n.children[0]
- 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[i] {
- parentFullPathIndex += len(n.path)
- i = n.incrementChildPrio(i)
- n = n.children[i]
- 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[len(n.children)-1]
- 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[len(n.path)] == '/') {
- continue walk
- }
- // Wildcard conflict
- pathSeg := path
- if n.nType != catchAll {
- pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
- }
- 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("[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
- }
- 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[i].method != httpMethod {
- continue
- }
- root := t[i].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树、字典树或键树,是一种树形数据结构,主要用于高效地存储和查询字符串。
其特点为:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上颠末的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
2、压缩前缀树
压缩前缀树是一种高效处理字符串聚集的数据结构,通过归并共享的前缀来节省存储空间并提高检索效率。它在网络路由、编译器符号表和字符串检索等领域有广泛应用。
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
- // 后续节点数量
- priority uint32
- // 孩子节点列表
- children []*node // child nodes, at most 1 :param style node at the end of the array
- // 处理函数链
- handlers HandlersChain
- fullPath string
- }
- type HandlersChain []HandlerFunc
复制代码 4、注册路由
- func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
- assert1(path[0] == '/', "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[i:],
- 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[i]})
- 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[i:]
- c := path[0]
- // '/' after param
- if n.nType == param && c == '/' && len(n.children) == 1 {
- parentFullPathIndex += len(n.path)
- n = n.children[0]
- 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[i] {
- parentFullPathIndex += len(n.path)
- i = n.incrementChildPrio(i)
- n = n.children[i]
- 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[len(n.children)-1]
- 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[len(n.path)] == '/') {
- continue walk
- }
- // Wildcard conflict
- pathSeg := path
- if n.nType != catchAll {
- pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
- }
- 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[len(prefix):]
- // Try all the non-wildcard children first by matching the indices
- idxc := path[0]
- for i, c := range []byte(n.indices) {
- if c == idxc {
- // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
- if n.wildChild {
- index := len(*skippedNodes)
- *skippedNodes = (*skippedNodes)[:index+1]
- (*skippedNodes)[index] = 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[i]
- 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)[length-1]
- *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[len(n.children)-1]
- globalParamsCount++
- switch n.nType {
- case param:
- // fix truncate the parameter
- // tree_test.go line: 204
- // Find param end (either '/' or path end)
- end := 0
- for end < len(path) && path[end] != '/' {
- 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)[i] = Param{
- Key: n.path[1:],
- Value: val,
- }
- }
- // we need to go deeper!
- if end < len(path) {
- if len(n.children) > 0 {
- path = path[end:]
- n = n.children[0]
- 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[0]
- 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)[i] = Param{
- Key: n.path[2:],
- 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)[length-1]
- *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[len(latestNode.children)-1]
- }
- // 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[i]
- value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
- (n.nType == catchAll && n.children[0].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[len(path)] == '/' &&
- 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)[length-1]
- *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 map[string]any
- // 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企服之家,中国第一个企服评测及商务社交产业平台。 |