From ddfc4de77214b04f1823e72a570f6dedcb703285 Mon Sep 17 00:00:00 2001 From: Douglas-Lee Date: Wed, 20 Dec 2023 00:38:51 +0800 Subject: [PATCH] code style: test all green --- benchmark/match-prefix.lua | 4 +- spec/router_spec.lua | 2 +- src/resty/router.lua | 2 + src/resty/trie.lua | 210 ++++++++++++++----------------------- 4 files changed, 83 insertions(+), 135 deletions(-) diff --git a/benchmark/match-prefix.lua b/benchmark/match-prefix.lua index 1571c34..56330c6 100644 --- a/benchmark/match-prefix.lua +++ b/benchmark/match-prefix.lua @@ -4,7 +4,7 @@ local match_times = 1000 * 1000 local routes = {} for i = 1, route_count do - routes[i] = {paths = {"/" .. ngx.md5(i) .. "/*"}, metadata = i} + routes[i] = {paths = {"/" .. (i) .. "/*"}, metadata = i} end local rx = radix.new(routes) @@ -13,7 +13,7 @@ ngx.update_time() local start_time = ngx.now() local res -local path = "/" .. ngx.md5(500) .. "/a" +local path = "/" .. 500 .. "/a" for _ = 1, match_times do res = rx:match(path) end diff --git a/spec/router_spec.lua b/spec/router_spec.lua index 0abdcc7..0bc5b0d 100644 --- a/spec/router_spec.lua +++ b/spec/router_spec.lua @@ -110,7 +110,7 @@ describe("Router", function() handler = "2", }, }) - assert.equal("1", router:match("/aa")) + assert.equal("1", router:match("/a")) end) describe("parameter matching", function() diff --git a/src/resty/router.lua b/src/resty/router.lua index 636876c..783aa6b 100644 --- a/src/resty/router.lua +++ b/src/resty/router.lua @@ -74,7 +74,9 @@ local function find_route(routes, ctx) if route:is_match(ctx) then return route end + return nil end + for _, route in ipairs(routes) do if route:is_match(ctx) then return route diff --git a/src/resty/trie.lua b/src/resty/trie.lua index 7577cf4..c395c95 100644 --- a/src/resty/trie.lua +++ b/src/resty/trie.lua @@ -37,8 +37,9 @@ function TrieNode.new(o) local self = { path = o.path or "", children = o.children, + children_n = o.children_n or 0, type = o.type, - value = o.value, -- 有没有可能这个 Trie 不做 value 的插入,只是返回最后的节点,但是 node.value 的设置交给外部,这样就可以根据 priority 做 value 优先级循序 + value = o.value, indexs = o.indexs or {}, full_path = o.full_path, -- 如果叶子节点可以有多个 values,那么这里的 full_path 不能代表完成的路径 } @@ -83,6 +84,7 @@ local function insert_child(node, path, full_path, value, fn) if token then local child = TrieNode.new() node.children = { child } + node.children_n = node.children_n + 1 local c = str_sub(token, 1, 1) --if c ~= ":" and c ~= "*" then node.indexs[c] = 1 @@ -100,6 +102,7 @@ local function split(node, path, prefix_n) path = str_sub(node.path, prefix_n + 1), full_path= node.full_path, children = node.children, + children_n = node.children_n, type = TYPES.STATIC, value = node.value, indexs = node.indexs, @@ -110,6 +113,7 @@ local function split(node, path, prefix_n) node.full_path = nil node.path_n = #node.path node.children = { child } + node.children_n = 1 node.type = TYPES.STATIC node.value = nil node.indexs = { [str_sub(child.path, 1, 1)] = 1 } @@ -127,84 +131,54 @@ function TrieNode:add(path, value, conflict_cb) local node = self while true do - -- 换成这样的逻辑吧? - -- while true - -- if condition - -- break; - ::continue:: - local prefix_n = lcp(path, self.path) + local common_prefix_n = lcp(node.path, path) - if prefix_n < self.path_n then - split(self, path, prefix_n) + if common_prefix_n < node.path_n then + split(node, path, common_prefix_n) end - if prefix_n < #path then - - - if self.type == TYPES.PARAM then - -- 当前的节点是 param 类型,所以应该是匹配的 - path = str_sub(path, prefix_n + 1) - self = self.children[1] - goto continue - elseif self.type == TYPES.CATCH_ALL then - -- 当前的节点是catch-all类型,而且剩余的 path 是 *xxx的 - -- 所以我们应该移除掉 *xxx 然后继续 - local first_char = str_sub(path, 1, 1) - if first_char == "*" then - local parser = Parser.new(path, "default") - parser:next() - path = str_sub(path, parser.pos) - if path == "" then - break - end + if common_prefix_n < #path then + if node.type == TYPES.PARAM then + local idx = find(path, "/", nil, true) + if idx then + path = str_sub(path, idx) + else + break end + elseif node.type == TYPES.CATCH_ALL then + break else - path = str_sub(path, prefix_n + 1) + path = str_sub(path, common_prefix_n + 1) end - local first_char = str_sub(path, 1, 1) - local index = self.indexs[first_char] - if index then - self = self.children[index] - if first_char == ":" then - local idx_slash = find(path, "/", nil, true) - if idx_slash then - path = ":" .. str_sub(path, idx_slash) - else - path = "" - break - end - end + if node.indexs[first_char] then + node = node.children[node.indexs[first_char]] goto continue end - -- 如果已经没有共同节点, - -- 最后插入到当前节点里? - - -- 创建一个新的节点 local child = TrieNode.new() - self.children = self.children or {} - table.insert(self.children, child) + node.children = node.children or {} + table.insert(node.children, child) + node.children_n = node.children_n + 1 insert_child(child, path, full_path, value, conflict_cb) - self.indexs[first_char] = #self.children + node.indexs[first_char] = #node.children -- use self.children_n? return end - self:set(value, conflict_cb) - return + break end - self:set(value, conflict_cb) + node:set(value, conflict_cb) end local matched_values = {} -- 叫做 visted_leaf 呢?表示所有访问过的叶子结点? function TrieNode:traverse(path, ctx) local binding_params = ctx.matched ~= nil - local param_i = 1 + local param_n = 0 local matched_n = 0 clear_table(matched_values) @@ -213,13 +187,14 @@ function TrieNode:traverse(path, ctx) local prefix local prefix_n local path_n + local idx while true do ::continue:: prefix = node.path prefix_n = node.path_n - path_n = #path + path_n = #path -- path_n 有没有可能可以通过游标去减,而不是每次都 #path? if path_n > prefix_n then -- path: /a/b/c @@ -227,26 +202,11 @@ function TrieNode:traverse(path, ctx) if starts_with(path, prefix, path_n, prefix_n) then path = str_sub(path, prefix_n + 1) - -- 如果当前的子节点有后缀匹配 - -- 要记录下来 - --local catch_all_i = 0 - --if catch_all_i then - -- local value = self.children[catch_all_i].value - -- if value then - -- table.insert(values, value) - -- end - --end - -- TODO 应该有索引 - -- 如果两个路劲冲突了呢?"/aa/*cat", "/aa/*doge" - -- /aa/ children - -- 1:*cat - -- 2:*doge - -- - local idx = node.indexs["*"] + idx = node.indexs["*"] if idx then - local n = node.children[idx] + -- 如果子节点中有 catchall 类型,说明 child 节点是能匹配的 matched_n = matched_n + 1 - matched_values[matched_n] = n.value + matched_values[matched_n] = node.children[idx].value -- todo 既然这里已经选中了,为什么不做参数绑定呢? end local first_char = str_sub(path, 1, 1) @@ -256,38 +216,48 @@ function TrieNode:traverse(path, ctx) goto continue end - -- 走到这里后说明当前node的indexs不包含剩下 path 的内容 - if not node.children or #node.children == 0 then - return nil, 0 + -- 到这里说明有 * 或者 : + -- 如果当前节点既有 * 和 : 子节点呢? + -- /a/* + -- /a/:name/doge + -- /a/:name/dog* + -- match("/a/john/doge") + -- ctx.matched 就会变成 { "john/doge", "john" } + -- 如果外面参数绑定的时候能先去掉 john,再去掉 "/john/doge" 倒叙,那应该是没问题的? + + -- 作用是啥? + idx = node.indexs["*"] + if idx then + matched_n = matched_n + 1 + matched_values[matched_n] = node.children[idx].value + if binding_params then + param_n = param_n + 1 + ctx.matched[param_n] = path + ctx.matched["_path"] = node.children[idx].full_path + end end - - -- 通配符 - - node = node.children[1] -- 为什么要选 [1] ? - if node.type == TYPES.PARAM then - -- 找到第一个 / - local idx = find(path, "/", 1, true) - - if ctx and ctx.matched then - -- parameter binding - --local param_name = sub(self.path, 2) -- :name - local param_value = str_sub(path, 1, idx and idx - 1) - --print("param: " .. param) - ctx.matched[param_i] = param_value - param_i = param_i + 1 - --ctx.matched[param_name] = param_value + idx = node.indexs[":"] + if idx then + node = node.children[idx] + local i = find(path, "/", 1, true) or #path + 1 -- todo 写一个 while 来实现 + i = i - 1 + if binding_params then + local param_value = str_sub(path, 1, i) + param_n = param_n + 1 + ctx.matched[param_n] = param_value end - - if idx and idx < #path then - if #node.children > 0 then - path = str_sub(path, idx) - --print(path) - node = node.children[1] - goto continue + if i < #path then -- 还没到终点 + if node.children_n > 0 then + path = str_sub(path, i + 1) + first_char = str_sub(path, 1, 1) + idx = node.indexs[first_char] + if idx then + node = node.children[idx] + goto continue + end end end - if node.value then if binding_params then ctx.matched["_path"] = node.full_path @@ -296,25 +266,6 @@ function TrieNode:traverse(path, ctx) matched_values[matched_n] = node.value break end - - elseif node.type == TYPES.CATCH_ALL then - -- 前缀匹配 - if node.value then - if binding_params then - --local param_name = sub(self.path, 2) - --if #param_name == 0 then - -- param_name = ":ext" - --end - --ctx.matched[param_name] = path - ctx.matched[param_i] = path - param_i = param_i + 1 - ctx.matched["_path"] = node.full_path - end - -- 前面的 mached_valued 已经记录过了 - break - end - else - --error("???") end end elseif path == prefix then @@ -327,20 +278,15 @@ function TrieNode:traverse(path, ctx) break end - for _, child in ipairs(node.children or EMPTY) do - if child.type == TYPES.CATCH_ALL then - if binding_params then - --local param_name = sub(child.path, 2) - --if #param_name == 0 then - -- param_name = ":ext" - --end - --ctx.matched[param_name] = "" - ctx.matched[param_i] = "" - param_i = param_i + 1 - ctx.matched["_path"] = child.full_path - end - matched_n = matched_n + 1 - matched_values[matched_n] = child.value + idx = node.indexs["*"] + if idx then + node = node.children[idx] + matched_n = matched_n + 1 + matched_values[matched_n] = node.value + if binding_params then + param_n = param_n + 1 + ctx.matched[param_n] = "" + ctx.matched["_path"] = node.full_path end end end