背景
gin框架采用了httprouter进行路由匹配,所以废话少说,这篇文章来帮你了解他的源码实现
树 httprouter的路由匹配功能是基于radix tree(前缀树/基数树)这种数据结构实现的: 有读者可能会有疑问:为什么不按照RUL分隔符切割的来分割,每个segment作为一个节点,而是要用公共前缀来分割?针对这个疑问,了解树的开发者都知道,树的高度是影响查找效率的重要因素,高度越高,查找效率越低。两张图片来对比一下:
源码 demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ( "fmt" "net/http" "log" "github.com/julienschmidt/httprouter" ) func Index (w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, "Welcome!\n" ) } func Hello (w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, "hello, %s!\n" , ps.ByName("name" )) } func main () { router := httprouter.New() router.GET("/" , Index) router.GET("/hello/:name" , Hello) log.Fatal(http.ListenAndServe(":8080" , router)) }
首先看到 httprouter.New(),因为它返回一个router实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func New () *Router { return &Router{ RedirectTrailingSlash: true , RedirectFixedPath: true , HandleMethodNotAllowed: true , HandleOPTIONS: true , } } type Router struct { trees map [string ]*node paramsPool sync.Pool maxParams uint16 SaveMatchedRoutePath bool RedirectTrailingSlash bool RedirectFixedPath bool HandleMethodNotAllowed bool HandleOPTIONS bool GlobalOPTIONS http.Handler globalAllowed string NotFound http.Handler MethodNotAllowed http.Handler PanicHandler func (http.ResponseWriter, *http.Request, interface {}) }
它实现了 ServeHTTP 这个函数,因此符合 net/http.Handler 接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (r *Router) ServeHTTP (w http.ResponseWriter, req *http.Request) { if r.PanicHandler != nil { defer r.recv(w, req) } path := req.URL.Path if root := r.trees[req.Method]; root != nil { if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil { if ps != nil { handle(w, req, *ps) r.putParams(ps) } else { handle(w, req, nil ) } return ... }
这就是处理请求的时候,查找路由树及handler的那部分,root.getValue就是查找路由树的具体函数,不过细节此处不表。
我们接下来看看注册路由的那部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (r *Router) GET (path string , handle Handle) { r.Handle(http.MethodGet, path, handle) } func (n *node) addRoute (path string , handle Handle) { fullPath := path n.priority++ if len (n.path) == 0 && len (n.indices) == 0 { n.insertChild(path, fullPath, handle) n.nType = root return } walk: for { ... }
而 addRoute 函数就是实现radix tree这个数据结构的函数了,它会先找到共同的部分,然后考虑是否把路由切分为字节点,最后 把handler写上去(调用 insertChild 函数)。
1 2 3 4 5 6 7 8 9 type node struct { path string indices string wildChild bool nType nodeType priority uint32 children []*node handle Handle }
node就是保存这些资料的radix tree的节点