diff --git a/Makefile b/Makefile index 054a077..ea7eb8b 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,7 @@ test: build luajit spec/utils_spec.lua test2: build - TEST_NGINX_LOG_LEVEL=info \ - prove -I../test-nginx/lib -I. -r -s t/ + TEST_NGINX_LOG_LEVEL=info prove -I../test-nginx/lib -I. -r -s t/ bench: build resty -I=./lib -I=./deps/share/lua/5.1 benchmark/match-parameter.lua diff --git a/README.md b/README.md index c4a4c20..b98e04a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # lua-resty-router-tree +兼容性: + +Lua>=5.2, LuaJIT, OpenResty, + +(`::continue::` 在 5.1 不支持) + + ```lua local routes = {} local router = router.new(routes) @@ -34,4 +41,4 @@ go httprouter TODO 处理路由冲突 - /user/:name/info -- /user/:id/info \ No newline at end of file +- /user/:id/info diff --git a/lua-resty-router-tree-dev-1.rockspec b/lua-resty-router-tree-dev-1.rockspec index 80f0754..61ea094 100644 --- a/lua-resty-router-tree-dev-1.rockspec +++ b/lua-resty-router-tree-dev-1.rockspec @@ -29,6 +29,7 @@ build = { type = "builtin", modules = { ["router-tree"] = "src/resty/router.lua", + ["router-tree.route"] = "src/resty/route.lua", ["router-tree.parser"] = "src/resty/parser/parser.lua", ["router-tree.parser.style.default"] = "src/resty/parser/style/default.lua", ["router-tree.parser.style.ant"] = "src/resty/parser/style/ant.lua", diff --git a/src/resty/route.lua b/src/resty/route.lua new file mode 100644 index 0000000..5371ac6 --- /dev/null +++ b/src/resty/route.lua @@ -0,0 +1,150 @@ +local utils = require "router-tree.utils" +local Parser = require "router-tree.parser" +local bit = utils.is_luajit and require "bit" + +local ipairs = ipairs +local is_luajit = utils.is_luajit +local starts_with = utils.starts_with + +local EMPTY = {} + +local METHODS = {} +for i, name in ipairs({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE", "PURGE"}) do + METHODS[name] = bit.lshift(1, i - 1) + -- ngx.log(ngx.WARN, "name: ", name, " val: ", METHODS[name]) +end + + + +local Route = {} +local mt = { __index = Route } + +local function extract_params(path) + local params = {} + local parser = Parser.parse(path, "default") + local token = parser:next() + while (token) do + local c = string.sub(token, 1, 1) + if c == ":" then + table.insert(params, string.sub(token, 2)) + elseif c == "*" then + if #token > 1 then + table.insert(params, string.sub(token, 2)) + else + table.insert(params, ":ext") + end + end + token = parser:next() + end + return params +end + +function Route.new(route) + + local self = { + priority = route.priority, + handler = route.handler or route.metadata, + params = {}, + } + + for _, path in ipairs(route.paths) do + self.params[path] = extract_params(path) + end + + if is_luajit then + local methods_bit = 0 + for _, method in ipairs(route.methods or EMPTY) do + methods_bit = bit.bor(methods_bit, METHODS[method]) + end + self.method = methods_bit + else + local methods = {} + for _, method in ipairs(route.methods or EMPTY) do + methods[method] = true + end + self.method = methods + end + + -- hosts + local hosts = route.hosts + if type(hosts) == "table" and #hosts > 0 then + self.hosts = {} + for _, h in ipairs(hosts) do + local is_wildcard = false + if h and h:sub(1, 1) == '*' then + is_wildcard = true + h = h:sub(2) + end + + h = string.lower(h) + table.insert(self.hosts, is_wildcard) + table.insert(self.hosts, h) + end + + elseif type(hosts) == "string" then + local is_wildcard = false + local host = string.lower(hosts) + if host:sub(1, 1) == '*' then + is_wildcard = true + host = host:sub(2) + end + + self.hosts = { is_wildcard, host} + end + + return setmetatable(self, mt) +end + +local function match_host(route_host_is_wildcard, route_host, request_host) + if not route_host_is_wildcard then + return route_host == request_host + end + + return starts_with(request_host, route_host) +end + + +function Route:is_match(ctx) + local route = self + + if route.method ~= 0 then + local method = ctx.method + -- 或者直接使用 map 就好 + if not method or METHODS[method] == nil or bit.band(route.method, METHODS[method]) == 0 then + return false + end + end + + -- log_info("route.hosts: ", type(route.hosts)) + if route.hosts then + local matched = false + + local hosts = route.hosts + local host = ctx.host + if host then + local len = #hosts + for i = 1, len, 2 do + if match_host(hosts[i], hosts[i + 1], host) then + if ctx and ctx.matched then + if hosts[i] then + ctx.matched._host = "*" .. hosts[i + 1] + else + ctx.matched._host = ctx.host + end + end + matched = true + break + end + end + end + + if not matched then + return false + end + end + + return true +end + + +return Route diff --git a/src/resty/router.lua b/src/resty/router.lua index 77fdba1..3fac745 100644 --- a/src/resty/router.lua +++ b/src/resty/router.lua @@ -1,24 +1,16 @@ local Trie = require "router-tree.trie" +local Route = require "router-tree.route" local utils = require "router-tree.utils" -local Parser = require "router-tree.parser" -local bit = require("bit") local ipairs = ipairs local has_wildcard = utils.has_wildcard local clear_table = utils.clear_table -local start_with = utils.start_with - local EMPTY = {} local Router = {} local mt = { __index = Router } -local METHODS = {} -for i, name in ipairs({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE", "PURGE"}) do - METHODS[name] = bit.lshift(1, i - 1) - -- ngx.log(ngx.WARN, "name: ", name, " val: ", METHODS[name]) -end local function sort_route(route_a, route_b) return (route_a.priority or 0) > (route_b.priority or 0) @@ -40,25 +32,6 @@ local function add_route(self, path, route) end end -local function extract_params(path) - local params = {} - local parser = Parser.parse(path, "default") - local token = parser:next() - while (token) do - local c = string.sub(token, 1, 1) - if c == ":" then - table.insert(params, string.sub(token, 2)) - elseif c == "*" then - if #token > 1 then - table.insert(params, string.sub(token, 2)) - else - table.insert(params, ":ext") - end - end - token = parser:next() - end - return params -end function Router.new(routes, options) options = options or EMPTY @@ -75,47 +48,12 @@ function Router.new(routes, options) } for _, route in ipairs(routes) do - local bit_methods = 0 - for _, m in ipairs(route.methods or EMPTY) do - bit_methods = bit.bor(bit_methods, METHODS[m]) - end - - local route_t = { - priority = route.priority, - handler = route.handler or route.metadata, - method = bit_methods, - params = {} - } - - -- hosts - local hosts = route.hosts - if type(hosts) == "table" and #hosts > 0 then - route_t.hosts = {} - for _, h in ipairs(hosts) do - local is_wildcard = false - if h and h:sub(1, 1) == '*' then - is_wildcard = true - h = h:sub(2) - end - - h = string.lower(h) - table.insert(route_t.hosts, is_wildcard) - table.insert(route_t.hosts, h) - end - - elseif type(hosts) == "string" then - local is_wildcard = false - local host = string.lower(hosts) - if host:sub(1, 1) == '*' then - is_wildcard = true - host = host:sub(2) - end - - route_t.hosts = {is_wildcard, host} + local route_t, err = Route.new(route) + if err then + return nil, err end for _, path in ipairs(route.paths) do - route_t.params[path] = extract_params(path) add_route(self, path, route_t) end end @@ -123,56 +61,10 @@ function Router.new(routes, options) return setmetatable(self, mt) end -local function match_host(route_host_is_wildcard, route_host, request_host) - if not route_host_is_wildcard then - return route_host == request_host - end - - return start_with(request_host, route_host) -end - -local function is_match(route, ctx) - if route.method ~= 0 then - local method = ctx.method - if not method or METHODS[method] == nil or bit.band(route.method, METHODS[method]) == 0 then - return false - end - end - - -- log_info("route.hosts: ", type(route.hosts)) - if route.hosts then - local matched = false - - local hosts = route.hosts - local host = ctx.host - if host then - local len = #hosts - for i = 1, len, 2 do - if match_host(hosts[i], hosts[i + 1], host) then - if ctx and ctx.matched then - if hosts[i] then - ctx.matched._host = "*" .. hosts[i + 1] - else - ctx.matched._host = ctx.host - end - end - matched = true - break - end - end - end - - if not matched then - return false - end - end - - return true -end local function find_route(routes, ctx) for _, route in ipairs(routes) do - if is_match(route, ctx) then + if route:is_match(ctx) then return route end end @@ -238,7 +130,7 @@ function Router:match(path, ctx) end end - if route and is_match(route, ctx) then + if route and route:is_match(ctx) then if ctx.matched then local _path = ctx.matched._path local params = route.params[_path] diff --git a/src/resty/sample.lua b/src/resty/sample.lua index 09df865..b448f3f 100644 --- a/src/resty/sample.lua +++ b/src/resty/sample.lua @@ -135,18 +135,19 @@ local function test_example() end local function test5() - local mobdebug = require "mobdebug" - local a, b, c = mobdebug.start("localhost", 28172) - print(a, b, c) local radix = require("router-tree") local rx = radix.new({ { - paths = {"/*"}, - metadata = "OK", + paths = {"/aa"}, + metadata = "metadata 1", + }, + { + paths = {"/aa"}, + metadata = "metadata 2", }, }) - local opts = {method = "GET", matched = {}} - ngx.say(rx:match("/ip\ni\ni", opts)) + + ngx.say(rx:match("/aa")) end --test1() diff --git a/src/resty/utils.lua b/src/resty/utils.lua index 1b3c128..d5aac20 100644 --- a/src/resty/utils.lua +++ b/src/resty/utils.lua @@ -2,6 +2,7 @@ local str_byte = string.byte local str_find = string.find local math_min = math.min +local is_luajit = type(_G.jit) == "table" local clear_table do @@ -20,8 +21,7 @@ end local starts_with do - local luajit = type(_G.jit) == "table" - if luajit then + if is_luajit then local ffi = require "ffi" local C = ffi.C ffi.cdef[[ @@ -82,4 +82,5 @@ return { has_wildcard = has_wildcard, clear_table = clear_table, breakpoint = breakpoint, + is_luajit = is_luajit, } diff --git a/t/priority.t b/t/priority.t index 00bfea5..2ee7b9c 100644 --- a/t/priority.t +++ b/t/priority.t @@ -1,3 +1,5 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + use t::RX 'no_plan'; repeat_each(1); @@ -9,7 +11,7 @@ __DATA__ --- config location /t { content_by_lua_block { - local radix = require("resty.radixtree") + local radix = require("router-tree") local rx = radix.new({ { paths = {"/aa"}, @@ -37,7 +39,7 @@ metadata 1 --- config location /t { content_by_lua_block { - local radix = require("resty.radixtree") + local radix = require("router-tree") local rx = radix.new({ { paths = {"/aa"}, diff --git a/t/servroot/conf/nginx.conf b/t/servroot/conf/nginx.conf index 865a115..91f8bec 100644 --- a/t/servroot/conf/nginx.conf +++ b/t/servroot/conf/nginx.conf @@ -42,21 +42,16 @@ http { # Begin test case config... location /t { content_by_lua_block { + local ffi = require("ffi") local radix = require("resty.radixtree") - local rx = radix.new({ - { - paths = {"/aa*"}, - metadata = "metadata /aa", - hosts = {"foo.com"}, - } - }) - - ngx.say(rx:match("/aa/bb", {host = "foo.com"})) - ngx.say(rx:match("/aa/bb", {host = "www.foo.com"})) - - local opts = {host = "foo.cOm", matched = {}} - rx:match("/aa/bb", opts) - ngx.say("matched: ", opts.matched._host) + local radix_symbols = radix._symbols + + local tree = nil + local foo = "foo" + local data_idx = radix_symbols.radix_tree_find(tree, foo, #foo) + + local idx = tonumber(ffi.cast('intptr_t', data_idx)) + ngx.say(idx) } } diff --git a/t/servroot/logs/access.log b/t/servroot/logs/access.log index 1f1b916..9705ff2 100644 --- a/t/servroot/logs/access.log +++ b/t/servroot/logs/access.log @@ -1 +1 @@ -127.0.0.1 - - [18/Dec/2023:15:38:20 +0800] "GET /t HTTP/1.1" 200 55 "-" "-" +127.0.0.1 - - [18/Dec/2023:17:07:07 +0800] "GET /t HTTP/1.1" 200 12 "-" "-" diff --git a/t/servroot/logs/error.log b/t/servroot/logs/error.log index 170e8b1..50c4c72 100644 --- a/t/servroot/logs/error.log +++ b/t/servroot/logs/error.log @@ -1,15 +1,13 @@ -2023/12/18 15:38:20 [notice] 20728#0: using the "kqueue" event method -2023/12/18 15:38:20 [notice] 20728#0: openresty/1.21.4.1 -2023/12/18 15:38:20 [notice] 20728#0: built by clang 14.0.3 (clang-1403.0.22.14.1) -2023/12/18 15:38:20 [notice] 20728#0: OS: Darwin 23.1.0 -2023/12/18 15:38:20 [notice] 20728#0: hw.ncpu: 10 -2023/12/18 15:38:20 [notice] 20728#0: net.inet.tcp.sendspace: 131072 -2023/12/18 15:38:20 [notice] 20728#0: kern.ipc.somaxconn: 128 -2023/12/18 15:38:20 [notice] 20728#0: getrlimit(RLIMIT_NOFILE): 12544:9223372036854775807 -2023/12/18 15:38:20 [info] 20729#0: *1 [lua] radixtree.lua:487: common_route_data(): path: /aa operator: <=, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" -2023/12/18 15:38:20 [info] 20729#0: *1 [lua] radixtree.lua:387: insert_route(): insert route path: /aa dataprt: 1, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" -2023/12/18 15:38:20 [notice] 20729#0: signal 3 (SIGQUIT) received from 20673, shutting down -2023/12/18 15:38:20 [info] 20729#0: kevent() failed (4: Interrupted system call) -2023/12/18 15:38:20 [notice] 20729#0: gracefully shutting down -2023/12/18 15:38:20 [notice] 20729#0: exiting -2023/12/18 15:38:20 [notice] 20729#0: exit +2023/12/18 17:07:07 [notice] 89643#0: using the "kqueue" event method +2023/12/18 17:07:07 [notice] 89643#0: openresty/1.21.4.1 +2023/12/18 17:07:07 [notice] 89643#0: built by clang 14.0.3 (clang-1403.0.22.14.1) +2023/12/18 17:07:07 [notice] 89643#0: OS: Darwin 23.1.0 +2023/12/18 17:07:07 [notice] 89643#0: hw.ncpu: 10 +2023/12/18 17:07:07 [notice] 89643#0: net.inet.tcp.sendspace: 131072 +2023/12/18 17:07:07 [notice] 89643#0: kern.ipc.somaxconn: 128 +2023/12/18 17:07:07 [notice] 89643#0: getrlimit(RLIMIT_NOFILE): 12544:9223372036854775807 +2023/12/18 17:07:07 [notice] 89644#0: signal 3 (SIGQUIT) received from 89616, shutting down +2023/12/18 17:07:07 [info] 89644#0: kevent() failed (4: Interrupted system call) +2023/12/18 17:07:07 [notice] 89644#0: gracefully shutting down +2023/12/18 17:07:07 [notice] 89644#0: exiting +2023/12/18 17:07:07 [notice] 89644#0: exit