diff --git a/README.md b/README.md index 9f82c821..fdd0f9ba 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,20 @@ Synopsis path = "/aa", metadata = "metadata /aa", host = "foo.com", - method = {"GET", "POST"}, + method = {"GET", "POST"}, -- multiple method remote_addr = "127.0.0.1", }, { path = "/bb*", metadata = "metadata /bb", - host = {"*.bar.com", "gloo.com"}, + host = {"*.bar.com", "gloo.com"}, -- multiple host method = {"GET", "POST", "PUT"}, remote_addr = "fe80:fe80::/64", - vars = { + vars = { -- multiple var {"arg_name", "==", "json"}, {"arg_weight", ">", "10"}, }, - filter_fun = function(vars) + filter_fun = function(vars) -- callback fun return vars["arg_name"] == "json" end, }, @@ -58,8 +58,12 @@ Synopsis path = "/cc", metadata = "metadata /cc", remote_addr = {"127.0.0.1","192.168.0.0/16", - "::1", "fe80::/32"} + "::1", "fe80::/32"} -- multiple remote_addr }, + { + path = {"/dd", "/dd/ee", "/dd/ff/*"}, -- multiple path + metadata = "metadata /dd and /dd/ee" + } }) -- try to match @@ -79,7 +83,7 @@ Methods new --- -`syntax: rx, err = radix:new(routes)` +`syntax: rx, err = radix.new(routes)` The routes is a array table, like `{ {...}, {...}, {...} }`, Each element in the array is a route, which is a hash table. @@ -87,11 +91,11 @@ The attributes of each element may contain these: |name |option |description| |-------- |--------|-----------| -|path |required|client request uri, the default is a full match. But if the end of the path is `*`, it means that this is a prefix path. For example `/foo*`, it'll match `/foo/bar` or `/foo/glo/grey` etc.| +|path |required|Client request uri, the default is a full match. But if the end of the path is `*`, it means that this is a prefix path. For example `/foo*`, it'll match `/foo/bar` or `/foo/glo/grey` etc. We can set multiple `path` by an array table, eg: `{"/", "/aa", "/bb"}`.| |metadata |option |Will return this field if using `rx:match` to match route.| |handler |option |Will call this function using `rx:dispatch` to match route.| -|host |option |Client request host, not only supports normal domain name, but also supports wildcard name, both `foo.com` and `*.foo.com` are valid.| -|remote_addr|option |Client remote address like `192.168.1.100`, and we can use CIDR format, eg `192.168.1.0/24`. BTW, In addition to supporting the IPv6 format, multiple IP addresses are allowed, this field will be an array table at this case, the elements of array shoud be a string IPv4 or IPv6 address.| +|host |option |Client request host, not only supports normal domain name, but also supports wildcard name, both `foo.com` and `*.foo.com` are valid. We can set multiple `host` by an array table, eg: `{"foo.com", "bar.com"}`.| +|remote_addr|option |Client remote address like `192.168.1.100`, and we can use CIDR format, eg `192.168.1.0/24`. BTW, In addition to supporting the IPv6 format, multiple IP addresses are allowed, this field will be an array table at this case, the elements of array shoud be a string IPv4 or IPv6 address. We can set multiple `remote_addr` by an array table, eg: `{"127.0.0.1", "192.0.0.0/8"}`.| |method |option |It's an array table, we can put one or more method names together. Here is the valid method list: "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT" and "TRACE".| |vars |option |It is an array of one or more {var, operator, val} elements. For example: {{var, operator, val}, {var, operator, val}, ...}. `{"arg_key", "==", "val"}` means the value of argument `key` expect to `val`.| |filter_fun |option |User defined filter function, We can use it to achieve matching logic for special scenes. `radixtree` will only pass one parameter which named `vars` when matching route.| diff --git a/lib/resty/radixtree.lua b/lib/resty/radixtree.lua index 0b4f0a70..4f585fcd 100644 --- a/lib/resty/radixtree.lua +++ b/lib/resty/radixtree.lua @@ -181,98 +181,108 @@ end do local route_opts = {} -function _M.new(routes) - if not routes then - return nil, "missing argument route" + +local function pre_insert_route(self, path, route) + if type(path) ~= "string" then + error("invalid argument path", 2) end - local route_n = #routes + if type(route.metadata) == "nil" and type(route.handler) == "nil" then + error("missing argument metadata or handler", 2) + end - local self = setmt__gc({ - tree = radix.radix_tree_new(), - match_data_index = 0, - match_data = new_tab(#routes, 0), - hash_path = new_tab(0, #routes), - }, mt) + if route.vars then + if type(route.vars) ~= "table" then + error("invalid argument vars", 2) + end + end - -- register routes - for i = 1, route_n do - local route = routes[i] + local method = route.method + local bit_methods + if type(method) ~= "table" then + bit_methods = method and METHODS[method] or 0 - if type(route.path) ~= "string" then - error("invalid argument path", 2) + else + bit_methods = 0 + for _, m in ipairs(method) do + bit_methods = bit.bor(bit_methods, METHODS[m]) end + end - if type(route.metadata) == "nil" and type(route.handler) == "nil" then - error("missing argument metadata or handler", 2) - end + clear_tab(route_opts) - if route.vars then - if type(route.vars) ~= "table" then - error("invalid argument vars", 2) + local host = route.host + if type(host) == "table" and #host > 0 then + route_opts.hosts = {} + for _, h in ipairs(host) do + local host_is_wildcard = false + if h and h:sub(1, 1) == '*' then + host_is_wildcard = true + h = h:sub(2):reverse() end - end - local method = route.method - local bit_methods - if type(method) ~= "table" then - bit_methods = method and METHODS[method] or 0 + insert_tab(route_opts.hosts, host_is_wildcard) + insert_tab(route_opts.hosts, h) + end - else - bit_methods = 0 - for _, m in ipairs(method) do - bit_methods = bit.bor(bit_methods, METHODS[m]) - end + elseif type(host) == "string" then + local host_is_wildcard = false + if host and host:sub(1, 1) == '*' then + host_is_wildcard = true + host = host:sub(2):reverse() end - clear_tab(route_opts) + route_opts.hosts = {host_is_wildcard, host} + end - local host = route.host - if type(host) == "table" and #host > 0 then - route_opts.hosts = {} - for _, h in ipairs(host) do - local host_is_wildcard = false - if h and h:sub(1, 1) == '*' then - host_is_wildcard = true - h = h:sub(2):reverse() - end + if path:sub(#path) == "*" then + path = path:sub(1, #path - 1) + route_opts.path_op = "<=" + else + route_opts.path_op = "=" + end + route_opts.path = path + + route_opts.metadata = route.metadata + route_opts.handler = route.handler + route_opts.method = bit_methods + route_opts.vars = route.vars + route_opts.filter_fun = route.filter_fun + + local err + route_opts.matcher_ins, err = parse_remote_addr(route.remote_addr) + if err then + error("invalid IP address: " .. err, 2) + end - insert_tab(route_opts.hosts, host_is_wildcard) - insert_tab(route_opts.hosts, h) - end + insert_route(self, route_opts) +end - elseif type(host) == "string" then - local host_is_wildcard = false - if host and host:sub(1, 1) == '*' then - host_is_wildcard = true - host = host:sub(2):reverse() - end +function _M.new(routes) + if not routes then + return nil, "missing argument route" + end - route_opts.hosts = {host_is_wildcard, host} - end + local route_n = #routes - local path = route.path - if path:sub(#path) == "*" then - path = path:sub(1, #path - 1) - route_opts.path_op = "<=" - else - route_opts.path_op = "=" - end - route_opts.path = path + local self = setmt__gc({ + tree = radix.radix_tree_new(), + match_data_index = 0, + match_data = new_tab(#routes, 0), + hash_path = new_tab(0, #routes), + }, mt) - route_opts.metadata = route.metadata - route_opts.handler = route.handler - route_opts.method = bit_methods - route_opts.vars = route.vars - route_opts.filter_fun = route.filter_fun + -- register routes + for i = 1, route_n do + local route = routes[i] + if type(route.path) == "string" then + pre_insert_route(self, route.path, route) - local err - route_opts.matcher_ins, err = parse_remote_addr(route.remote_addr) - if err then - error("invalid IP address: " .. err, 2) + else + for _, path in ipairs(route.path) do + pre_insert_route(self, path, route) + end end - - insert_route(self, route_opts) end return self diff --git a/t/path.t b/t/path.t new file mode 100644 index 00000000..fcc19e29 --- /dev/null +++ b/t/path.t @@ -0,0 +1,69 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use t::RX 'no_plan'; + +repeat_each(1); +run_tests(); + +__DATA__ + +=== TEST 1: single path +--- config + location /t { + content_by_lua_block { + local radix = require("resty.radixtree") + local rx = radix.new({ + { + path = {"/"}, + metadata = "metadata /", + }, + }) + ngx.say(rx:match("/xx")) + ngx.say(rx:match("/")) + } + } +--- request +GET /t +--- no_error_log +[error] +--- response_body +nil +metadata / + + + +=== TEST 2: multiple path +--- config + location /t { + content_by_lua_block { + local radix = require("resty.radixtree") + local rx = radix.new({ + { + path = {"/", "/aa", "/bb"}, + metadata = "metadata multipe path 1", + }, + { + path = {"/cc"}, + metadata = "metadata multipe path 2", + }, + }) + + ngx.say(rx:match("/")) + ngx.say(rx:match("/aa")) + ngx.say(rx:match("/bb")) + ngx.say(rx:match("/cc")) + ngx.say(rx:match("/dd")) + ngx.say(rx:match("/ee")) + } + } +--- request +GET /t +--- no_error_log +[error] +--- response_body +metadata multipe path 1 +metadata multipe path 1 +metadata multipe path 1 +metadata multipe path 2 +nil +nil