• go web framework gin 路由表的设计


    在上一篇go web framework gin 启动流程分析这一篇文章中,我分析了go gin启动的过程,在这一篇文章中我将继续上面的分析,讨论gin 中路由表是如何设计的?

    首先查看engine.handleHTTPRequest() 这个方法的定义:

    func (engine *Engine) handleHTTPRequest(c *Context) {
    	httpMethod := c.Request.Method //获取Request method
    	path := c.Request.URL.Path //获取 Request URL Path
    	unescape := false
    	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
    		path = c.Request.URL.RawPath
    		unescape = engine.UnescapePathValues
    	}
    
    	// Find root of the tree for the given HTTP method
    	t := engine.trees
    	for i, tl := 0, len(t); i < tl; i++ { //遍历每一个methord tree
    		if t[i].method != httpMethod {
    			continue
    		}
    		root := t[i].root
    		// Find route in tree
    		handlers, params, tsr := root.getValue(path, c.Params, unescape) //如果找到这个method tree, 那么就从这个tree中根据path,params, unescape 找到对应的handlers
    		if handlers != nil { //执行handler
    			c.handlers = handlers
    			c.Params = params
    			c.Next()
    			c.writermem.WriteHeaderNow()
    			return
    		}
                    //异常处理部分
    		if httpMethod != "CONNECT" && path != "/" {
    			if tsr && engine.RedirectTrailingSlash {
    				redirectTrailingSlash(c)
    				return
    			}
    			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
    				return
    			}
    		}
    		break
    	}
            //处理不能响应的method
    	if engine.HandleMethodNotAllowed {
    		for _, tree := range engine.trees {
    			if tree.method == httpMethod {
    				continue
    			}
    			if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
    				c.handlers = engine.allNoMethod
    				serveError(c, http.StatusMethodNotAllowed, default405Body)
    				return
    			}
    		}
    	}
    	c.handlers = engine.allNoRoute
    	serveError(c, http.StatusNotFound, default404Body)
    }
    

     抛开其它的部分不看,只看如何根据path, nil, unescape 获取到handlers.

    // getValue returns the handle registered with the given path (key). The values of
    // wildcards are saved to a map.
    // If no handle can be found, a TSR (trailing slash redirect) recommendation is
    // made if a handle exists with an extra (without the) trailing slash for the
    // given path.
    func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) {
    	p = po
    walk: // Outer loop for walking the tree
    	for {
    		if len(path) > len(n.path) {
    			if path[:len(n.path)] == n.path {
    				path = path[len(n.path):]
    				// If this node does not have a wildcard (param or catchAll)
    				// child,  we can just look up the next child node and continue
    				// to walk down the tree
    				if !n.wildChild {
    					c := path[0]
    					for i := 0; i < len(n.indices); i++ {
    						if c == n.indices[i] {
    							n = n.children[i]
    							continue walk
    						}
    					}
    
    					// Nothing found.
    					// We can recommend to redirect to the same URL without a
    					// trailing slash if a leaf exists for that path.
    					tsr = path == "/" && n.handlers != nil
    					return
    				}
    
    				// handle wildcard child
    				n = n.children[0]
    				switch n.nType {
    				case param:
    					// find param end (either '/' or path end)
    					end := 0
    					for end < len(path) && path[end] != '/' {
    						end++
    					}
    
    					// save param value
    					if cap(p) < int(n.maxParams) {
    						p = make(Params, 0, n.maxParams)
    					}
    					i := len(p)
    					p = p[:i+1] // expand slice within preallocated capacity
    					p[i].Key = n.path[1:]
    					val := path[:end]
    					if unescape {
    						var err error
    						if p[i].Value, err = url.QueryUnescape(val); err != nil {
    							p[i].Value = val // fallback, in case of error
    						}
    					} else {
    						p[i].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
    						tsr = len(path) == end+1
    						return
    					}
    
    					if handlers = n.handlers; handlers != nil {
    						return
    					}
    					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]
    						tsr = n.path == "/" && n.handlers != nil
    					}
    
    					return
    
    				case catchAll:
    					// save param value
    					if cap(p) < int(n.maxParams) {
    						p = make(Params, 0, n.maxParams)
    					}
    					i := len(p)
    					p = p[:i+1] // expand slice within preallocated capacity
    					p[i].Key = n.path[2:]
    					if unescape {
    						var err error
    						if p[i].Value, err = url.QueryUnescape(path); err != nil {
    							p[i].Value = path // fallback, in case of error
    						}
    					} else {
    						p[i].Value = path
    					}
    
    					handlers = n.handlers
    					return
    
    				default:
    					panic("invalid node type")
    				}
    			}
    		} else if path == n.path {
    			// We should have reached the node containing the handle.
    			// Check if this node has a handle registered.
    			if handlers = n.handlers; handlers != nil {
    				return
    			}
    
    			if path == "/" && n.wildChild && n.nType != root {
    				tsr = true
    				return
    			}
    
    			// No handle found. Check if a handle for this path + a
    			// trailing slash exists for trailing slash recommendation
    			for i := 0; i < len(n.indices); i++ {
    				if n.indices[i] == '/' {
    					n = n.children[i]
    					tsr = (len(n.path) == 1 && n.handlers != nil) ||
    						(n.nType == catchAll && n.children[0].handlers != nil)
    					return
    				}
    			}
    
    			return
    		}
    
    		// Nothing found. We can recommend to redirect to the same URL with an
    		// extra trailing slash if a leaf exists for that path
    		tsr = (path == "/") ||
    			(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
    				path == n.path[:len(n.path)-1] && n.handlers != nil)
    		return
    	}
    }
    

     实际上这部分的实现以及insertChild 和 addRoute部分都是基于基树(Radix tree)实现的。关于基树的知识,参考:待研究的那些经典算法

    再看看net/http中路由表的设计原理:参考,https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.4.md

    它是基于Map去实现的,我们知道Map的实现原理是基于哈希表+红黑树来实现,这种设计的好处就是在数据量少的情况下很快,但是占用空间很多。

    相比而言,gin的路由表的设计占用的内存就很少,同时查找数据也很快。

  • 相关阅读:
    itext itextpdf 版本概要介绍
    github上的优秀项目和开发环境配置
    tfs2012 的体验地址
    Linux网卡攻略
    展现图表JavaScript库 Javascript web charts [参考地址]
    JavaEE的几个参考
    dsl boo3个例子 9
    已读的和在读的一些C++经典图书
    WinRT参考资料
    Linux性能监测工具
  • 原文地址:https://www.cnblogs.com/Spider-spiders/p/10233892.html
Copyright © 2020-2023  润新知