「server.serve => conn.serve ==>defer cancleCtx/ w.cancelCtx() 」node
所以, 最要緊的就是看tree.go裏面定義的數據結構。曹大的圖能夠幫助理解。python
- 具體的路由規則要爛熟,由此解讀node的幾個類型
- priority/wildchild/indices的意思和做用
- test文件的測試有沒有沒覆蓋到的狀況?
type node struct { path string indices string wildChild bool nType nodeType priority uint32 children []*node handle Handle }
-pathgolang
一截兒path,絕非全路徑,這點能夠肯定。具體怎麼表述?
// incrementChildPrio if newPos != pos { n.indices = n.indices[:newPos] + n.indices[pos:pos+1] + n.indices[newPos:pos] + n.indices[pos+1:] }
可見每一個node爲chidren指定惟一的一字節字符,和排好序的chidren slice一一對應。問題是怎麼指定的呢?
是什麼?幹嗎的?
幹嗎的?
- 事實上,在router.go中,只用到了addRoute爲咱們的router添加路由以及對應的handle, insertChild爲addRoute服務。
- 進一步的事實是,調用addRoute的老是根節點root.
// router.go Handle if root == nil { root = new(node) r.trees[method] = root // 其餘邏輯 ]}
// tree.go addRoute fullpath := path n.priority ++ //Empty tree if len(n.path) == 0 && len(n.indices) == 0 { n.insertChild(path, fullpath, handle) n.nType == root // 常量: root return }
- 沒有「:」「*」都好說,直接:
//insertChild n.path = path n.handle = handle
- 有呢?有點複雜。將狀況簡單化,若是以前是空樹的話(如今要有第一個節點了),咱們想一想,有「:」「*」有什麼說頭?
- 問題一: 遇到無效的wildcard怎麼處理?
- 問題二:可能要設置wildcard衝突,怎麼設置呢?
if !valid { panic("only one wildcard per path segment is allowed, has: '" + wildcard + "' in path '" + fullPath + "'") }
if wildcard[0] == ':' { // param if i > 0 { // Insert prefix before the current wildcard n.path = path[:i] path = path[i:] } n.wildChild = true child := &node{ nType: param, path: wildcard, } n.children = []*node{child} n = child n.priority++ // If the path doesn't end with the wildcard, then there will be another non-wildcard subpath starting with '/' if len(wildcard) < len(path) { path = path[len(wildcard):] child := &node{ priority: 1, } n.children = []*node{child} n = child continue } // Otherwise we're done. Insert the handle in the new leaf n.handle = handle return }
if i > 0 { n.path = path[:i] path = path[i:] }
n.wildchild = true
child := &node{ nType: param, path: wildcard, } n.children = []*node{child} n = child n.priority++
n.handle = handle return
if len(wildcard) < len(path) { path = path[len(wildcard):] child := &node{ priority: 1, } n.children = []*node{child} n = child continue }
if i+len(wildcard) < len(path) { panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") }
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") }
i-- if path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'")}
小結一下: 調用插入方法的node的path會是參數path的前綴,是空串最保險了。參數path不會以「*」開頭, 以「:」開頭是可能的。node不會是param類型(catch也不會)。
n.path = path[:i] // First node: catchAll node with empty path child := &node{ wildChild: true, nType: catchAll, } n.children = []*node{child} n.indices = string('/') n = child n.priority++ // Second node: node holding the variable child = &node{ path: path[i:], nType: catchAll, handle: handle, priority: 1, } n.children = []*node{child} return
回到addRoute吧。數據結構
// 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)
再回顧一遍insertChild方法: 設想向空樹插入:「/:name」 , ":name", "/*catchall", 根結點不會帶「:」「*」,要麼空串, 要麼"/"。注意插入「*catchall」非法(index out of range!!!)。能夠理解他的註釋。
if i < len(n.path) { child := node{ path: n.path[i:], wildChild: n.wildChild, nType: static, indices: n.indices, children: n.children, handle: n.handle, priority: n.priority - 1, } n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 n.indices = string([]byte{n.path[i]}) n.path = path[:i] n.handle = nil n.wildChild = false }
-n.indices的含義。從上能夠瞥見一點端倪。記得咱們說過,n.indices 和 n.children一一對應?這裏n.indices就是這個child的開頭字母,正如其名,起到的是索引的做用。架構
if n.handle != nil { panic("a handle is already registered for path '" + fullPath + "'") } n.handle = handle
if i < len(path) { path = path[i:] // 節點n的子節點是通配,殺傷力比較大,先掌權,後清洗 if n.wildchild { // 1. 掌權 // 2.清洗,生存者能夠進入下一輪。 // 思考: // 1. 什麼樣的能逃過清洗? // 2. 進入下一輪的初始狀態是怎樣的: 虛擬根結點 } // 如今面臨的狀況:n的孩子節點沒有param或catchall //可是n自己多是 idxc := path[0] // 1. 一種簡單的狀況的處理 n是param,只有一個孩子 // 那必然是「/」開頭的或空串,直接重走循環 // 2. n的某個孩子和path有公共前綴,提高priority並重走循環 // 3.其餘狀況插入新的子節點,使樹增加 // 如今的情形是: n.path 和 path 絕對並無公共前綴了 // 3.1 等待拼接的全新path不以":" "*" 打頭,須要作一點工做 // 插入空節點,做用不明, 保護做用? // 結合insertChild的狀況,有個狀況能夠排除: // 若是 idxc是「:」 "*" 打頭的,n必然沒孩子 // 若是n是param, path是普通字母打頭的呢?按理來講,這是個不合法現象。這在上面的判斷(wildcard衝突判斷中)已經槍斃。 // 建立孩子(空串),插入此節點。 n.insertChild(path, fullpPath, handle) } }
- 公共前綴不爲空或n.path爲空串
- 若n爲param(模擬根結點沒法作到的), path必定是「:param」 或 「:param/」 或 「/」打頭三種狀況之一。
- param子節點如有必定是獨生子!!
- param沒孩子的話,要加一個空串「」
源碼參考:app
if i < len(path) { path = path[i:] if n.wildchild { n = n.children[0] n.prority ++ // Check if the wildcard mathes if len(path) >= len(n.path) && n.path == path[:len(n.path)] && n.nType != catchAll && (len(n.path) >= len(path) || path[len(n.path)] == "/") { continue walk } else { pathSeg := path if n.nType != cathAll { 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 + "'") } } idxc := path[0] if n.nType == param && idxc == "/" && len(n.children) == 1 { n = n.children[0] n.prority++ continue walk } for i, c := range []byte(n.indices) { if c == idxc { i = n.incrementChildPrio(i) n = n.children[i] continue walk } } if idxc != ':' && idxc != '*' { n.indices += string([]byte{idxc}) child := &node{} n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } n.insertChild(path, fullpPath, handle) }
做用猜測:根據樹,匹配手頭的path,把全部參數取出來,放在從sync.Pool裏拿出臨時對象[]Params, 不斷把解析的param加入。 catchall怎麼處理?拭目以待!函數