Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,33 @@ 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,
},
{
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
Expand All @@ -79,19 +83,19 @@ 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.

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.|
Expand Down
152 changes: 81 additions & 71 deletions lib/resty/radixtree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions t/path.t
Original file line number Diff line number Diff line change
@@ -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