同一个路径用 GET 和 POST 注册,在 Gin 里是两棵完全独立的树。这篇看它们怎么存放、怎么找、顺序由什么决定,以及“方法很多”时线性扫描的代价到底有多大。
tree.go(Gin v1.12.0):
45 | type methodTree struct {
46 | method string
47 | root *node
48 | }
49 |
50 | type methodTrees []methodTree
51 |
52 | func (trees methodTrees) get(method string) *node {
53 | for _, tree := range trees {
54 | if tree.method == method {
55 | return tree.root
56 | }
57 | }
58 | return nil
59 | }Engine.trees 是个切片,每项记录一个方法和它的根节点。找方法的树就是遍历切片比较字符串,找不到返回 nil——于是 handleHTTPRequest 里“这个方法没有树”和“这个方法的树里没有这条路由”是两种不同的失败路径。
trees 的顺序: [GET POST DELETE]
[GET]
"/users" [root prio=2 indices="/"] handlers=1 full="/users"
"/" [static prio=1 indices="" wildChild]
":id" [param prio=1 indices=""] handlers=1 full="/users/:id"
[POST]
"/users" [root prio=2 indices="/"] handlers=1 full="/users"
"/" [static prio=1 indices="" wildChild]
":id" [param prio=1 indices=""]
"/avatar" [static prio=1 indices=""] handlers=1 full="/users/:id/avatar"
[DELETE]
"/users/" [root prio=1 indices="" wildChild]
":id" [param prio=1 indices=""] handlers=1 full="/users/:id"同一个路径在 GET、POST、DELETE 里互不影响:POST 注册了 /users/:id/avatar,GET 的树里就没有它。树的创建发生在该方法的第一次注册,所以 trees 的顺序是各方法第一次出现的顺序(405 的 Allow 头也是按这个顺序拼的)。
routergroup.go 的 Any(Gin v1.12.0):
147 | func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
148 | for _, method := range anyMethods {
149 | group.handle(method, relativePath, handlers)
150 | }
151 |
152 | return group.returnObj()
153 | }
Any 之后 trees: [GET POST PUT PATCH HEAD OPTIONS DELETE CONNECT TRACE] 共 9 棵
Handle("get") → panic: http method get is not valid
Handle("M-SEARCH") → panic: http method M-SEARCH is not valid
Handle("PURGE") → 注册成功,trees=[PURGE]
Handle("LINK") → 注册成功,trees=[LINK]
Handle("") → panic: http method is not valid
Handle("G E T") → panic: http method G E T is not validAny 为 9 个标准方法各创建一棵树,如果只是想让 GET 和 POST 都能访问,分别注册这两个方法就够了,不必为 CONNECT、TRACE 也建树。自定义方法(比如缓存清理用的 PURGE)通过 Handle 注册,方法名必须全是大写英文字母,M-SEARCH 这种带连字符的会被拒绝。
trees: 26 棵,第 1 棵 XA ,第 26 棵 XZ
第 1 轮
第 1 棵树的方法 XA 中位 21.7 ns/op(最小 21.6 / 最大 21.8)
第 13 棵树的方法 XM 中位 50.8 ns/op(最小 50.7 / 最大 51.0)
第 26 棵树的方法 XZ 中位 84.2 ns/op(最小 84.0 / 最大 84.6)
第 2 轮
第 1 棵树的方法 XA 中位 21.8 ns/op(最小 21.6 / 最大 22.1)
第 13 棵树的方法 XM 中位 51.0 ns/op(最小 50.8 / 最大 51.8)
第 26 棵树的方法 XZ 中位 84.3 ns/op(最小 83.7 / 最大 84.6)每多一棵树大约多 2.5 ns,26 棵树里最后一棵比第一棵慢约 62 ns。正常服务只有几个方法,GET 又通常最先注册,这个代价可以忽略;只有大量自定义方法、并且把热点方法放在最后注册时才需要关心。