在上一篇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的路由表的设计占用的内存就很少,同时查找数据也很快。