HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go. In contrast to the default mux of Go’s net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better. The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.
type Handle func(http.ResponseWriter, *http.Request, Params) Handle is a function that can be registered to a route to handle HTTP requests. Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
例如前面示例中的Index()和Hello()都是Handle类型的实例。
1 2 3 4 5 6 7
funcIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, "Welcome!\n") }
type Param struct { Key string Value string } Param is a single URL parameter, consisting of a key and a value.
type Params []Param Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.
func(ps Params) ByName(name string) string ByName returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.
/blog/go/request-routers match: category="go", post="request-routers" /blog/go/request-routers/ no match, but the router would redirect /blog/go/ no match /blog/go/request-routers/comments no match
*name的捕获方式是从指定位置开始(包含前缀”/“)匹配到结尾:
1 2 3 4 5 6
Path: /files/*filepath
/files/ match: filepath="/" /files/LICENSE match: filepath="/LICENSE" /files/templates/article.html match: filepath="/templates/article.html" /files no match, but the router would redirect
// non-empty tree iflen(n.path) > 0 || len(n.children) > 0 { walk: for { // Update maxParams of the current node if numParams > n.maxParams { n.maxParams = numParams }
//查询最长公共前缀 i := 0 max := min(len(path), len(n.path)) for i < max && path[i] == n.path[i] { i++ }
// Update maxParams (max of all children) for i := range child.children { if child.children[i].maxParams > child.maxParams { child.maxParams = child.children[i].maxParams } }
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绝对可以成为注册映射的祖先节点 //找到最匹配的祖先节点作为自己的父节点 if i < len(path) { path = path[i:]
if n.wildChild { n = n.children[0] n.priority++
// Update maxParams of the child node if numParams > n.maxParams { n.maxParams = numParams } numParams--
// Check if the wildcard matches iflen(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 } else { // Wildcard conflict var pathSeg string if n.nType == catchAll { pathSeg = path } else { pathSeg = strings.SplitN(path, "/", 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 + "'") } }
c := path[0]
// slash after param if n.nType == param && c == '/' && len(n.children) == 1 { n = n.children[0] n.priority++ continue walk }
// Check if a child with the next path byte exists for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { i = n.incrementChildPrio(i) n = n.children[i] continue walk } } //插入子节点 // Otherwise insert it if c != ':' && c != '*' { // []byte for proper unicode char conversion, see #65 n.indices += string([]byte{c}) child := &node{ maxParams: numParams, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } n.insertChild(numParams, path, fullPath, handle) return
} elseif i == len(path) { // Make node a (in-path) leaf if n.handle != nil { panic("a handle is already registered for path '" + fullPath + "'") } n.handle = handle } return } } else { //树是空的,直接作为root n.insertChild(numParams, path, fullPath, handle) n.nType = root } }
if root := r.trees[req.Method]; root != nil { //查询前缀树 if handle, ps, tsr := root.getValue(path); handle != nil { //handle请求 handle(w, req, ps) return } elseif req.Method != http.MethodConnect && path != "/" { code := 301// Permanent redirect, request with GET method if req.Method != http.MethodGet { // Temporary redirect, request with same method // As of Go 1.3, Go does not support status code 308. code = 307 }
// Handle 404 if r.NotFound != nil { r.NotFound.ServeHTTP(w, req) } else { http.NotFound(w, req) } } func(n *node) getValue(path string) (handle Handle, p Params, tsr bool) { walk: // outer loop for walking the tree for { //查询前缀树节点 iflen(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.handle != 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 p == nil { // lazy allocation p = make(Params, 0, n.maxParams) } i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[1:] p[i].Value = path[:end]
// we need to go deeper! if end < len(path) { iflen(n.children) > 0 { path = path[end:] n = n.children[0] continue walk }
// ... but we can't tsr = (len(path) == end+1) return }
if handle = n.handle; handle != nil { return } elseiflen(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.handle != nil) }
return
case catchAll: // save param value if p == nil { // lazy allocation p = make(Params, 0, n.maxParams) } i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[2:] p[i].Value = path
handle = n.handle return
default: panic("invalid node type") } } } elseif path == n.path { // We should have reached the node containing the handle. // Check if this node has a handle registered. if handle = n.handle; handle != 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.handle != nil) || (n.nType == catchAll && n.children[0].handle != 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.handle != nil) return } }