Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor with lua-resty-expr #77

Merged
merged 2 commits into from
Dec 3, 2020
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
17 changes: 1 addition & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
- [Full path match](#full-path-match)
- [Prefix match](#prefix-match)
- [Parameters in path](#parameters-in-path)
- [Operator List](#operator-list)
- [match](#match)
- [dispatch](#dispatch)
- [Install](#install)
Expand Down Expand Up @@ -99,7 +98,7 @@ The attributes of each element may contain these:
|hosts |option |A list of client request host, not only supports normal domain name, but also supports wildcard name.|{"foo.com", "*.bar.com"}|
|remote_addrs|option |A list of client remote address(IPv4 and IPv6), and we can use CIDR format, eg `192.168.1.0/24`.|{"127.0.0.1", "192.0.0.0/8", "::1", "fe80::/32"}|
|methods |option |A list of method name. Here is full valid method list: "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT" and "TRACE".|{"GET", "POST"}|
|vars |option |A list of `{var, operator, val}`. For example: {{var, operator, val}, {var, operator, val}, ...}, `{"arg_name", "==", "json"}` means the value of argument `name` expect to `json`. Here is the full [Operator List](#operator-list).|{{"arg_name", "==", "json"}, {"arg_age", ">", 18}}|
|vars |option |A DSL to evaluate with the given `opts.vars` or `ngx.var`. See https://github.com/api7/lua-resty-expr#new |{{"arg_name", "==", "json"}, {"arg_age", ">", 18}}|
|filter_fun |option |User defined filter function, We can use it to achieve matching logic for special scenes. `radixtree` will pass `vars` and other arguments when matching route.|function(vars) return vars["arg_name"] == "json" end|
|priority |option |Routing priority, default is 0.|priority = 100|
|metadata |option |Will return this field if using `rx:match` to match route.||
Expand Down Expand Up @@ -162,20 +161,6 @@ local rx = radix.new({
})
```

#### Operator List

|operator|description|example|
|--------|-----------|-------|
|== |equal |{"arg_name", "==", "json"}|
|~= |not equal |{"arg_name", "~=", "json"}|
|> |greater than|{"arg_age", ">", 24}|
|< |less than |{"arg_age", "<", 24}|
|~~ |Regular match|{"arg_name", "~~", "[a-z]+"}|
|in |find in array|{"arg_name", "in", {"1","2"}}|
|has |left value array has value in the right |{"graphql_root_fields", "has", "repo"}|

[Back to TOC](#table-of-contents)

### match

`syntax: metadata = rx:match(path, opts)`
Expand Down
125 changes: 27 additions & 98 deletions lib/resty/radixtree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ local ipmatcher = require("resty.ipmatcher")
local base = require("resty.core.base")
local clone_tab = require("table.clone")
local lrucache = require("resty.lrucache")
local expr = require("resty.expr.v1")
local bit = require("bit")
local ngx = ngx
local table = table
Expand Down Expand Up @@ -163,6 +164,7 @@ end

local ngx_log = ngx.log
local ngx_INFO = ngx.INFO
local ngx_ERR = ngx.ERR
local function log_info(...)
if cur_level and ngx_INFO > cur_level then
return
Expand All @@ -171,6 +173,13 @@ local function log_info(...)
return ngx_log(ngx_INFO, ...)
end

local function log_err(...)
if cur_level and ngx_ERR > cur_level then
return
end

return ngx_log(ngx_ERR, ...)
end

local mt = { __index = _M, __gc = gc_free }

Expand Down Expand Up @@ -256,12 +265,6 @@ function pre_insert_route(self, path, route)
error("missing argument metadata or handler", 2)
end

if route.vars then
if type(route.vars) ~= "table" then
error("invalid argument vars", 2)
end
end

local method = route.methods
local bit_methods
if type(method) ~= "table" then
Expand All @@ -276,6 +279,18 @@ function pre_insert_route(self, path, route)

clear_tab(route_opts)

if route.vars then
if type(route.vars) ~= "table" then
error("invalid argument vars", 2)
end

local route_expr, err = expr.new(route.vars)
if not route_expr then
error("failed to handle expression: " .. err, 2)
end
route_opts.vars = route_expr
end

local hosts = route.hosts
if type(hosts) == "table" and #hosts > 0 then
route_opts.hosts = {}
Expand Down Expand Up @@ -330,7 +345,6 @@ function pre_insert_route(self, path, route)
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
route_opts.priority = route.priority or 0

Expand Down Expand Up @@ -485,86 +499,6 @@ local function compare_param(req_path, route, opts)
return true
end

local function in_array(l_v, r_v)
if type(r_v) == "table" then
for _,v in ipairs(r_v) do
if v == l_v then
return true
end
end
end
return false
end

local function has_element(l_v, r_v)
if type(l_v) == "table" then
for _, v in ipairs(l_v) do
if v == r_v then
return true
end
end

return false
end

return false
end

local compare_funcs = {
["=="] = function (l_v, r_v)
if type(r_v) == "number" then
l_v = tonumber(l_v)
if not l_v then
return false
end
end
return l_v == r_v
end,
["~="] = function (l_v, r_v)
return l_v ~= r_v
end,
[">"] = function (l_v, r_v)
l_v = tonumber(l_v)
r_v = tonumber(r_v)
if not l_v or not r_v then
return false
end
return l_v > r_v
end,
["<"] = function (l_v, r_v)
l_v = tonumber(l_v)
r_v = tonumber(r_v)
if not l_v or not r_v then
return false
end
return l_v < r_v
end,
["~~"] = function (l_v, r_v)
local from = re_find(l_v, r_v, "jo")
if from then
return true
end
return false
end,
["IN"] = in_array,
["in"] = in_array,
["has"] = has_element,
}


local function compare_val(l_v, op, r_v, opts)
if r_v == ngx_null then
r_v = nil
end

local com_fun = compare_funcs[op or "=="]
if not com_fun then
return false
end
return com_fun(l_v, r_v, opts)
end


local function match_route_opts(route, opts, args)
local method = opts.method
local opts_matched_exists = (opts.matched ~= nil)
Expand Down Expand Up @@ -621,18 +555,13 @@ local function match_route_opts(route, opts, args)
end

if route.vars then
local vars = opts.vars or ngx_var
if type(vars) ~= "table" then
return false
end

for _, route_var in ipairs(route.vars) do
local l_v, op, r_v = route_var[1], route_var[2], route_var[3]
l_v = vars[l_v]

if not compare_val(l_v, op, r_v, opts) then
return false
local ok, err = route.vars:eval(opts.vars, opts)
if not ok then
if ok == nil then
log_err("failed to eval expression: ", err)
end

return false
end
end

Expand Down
1 change: 1 addition & 0 deletions rockspec/lua-resty-radixtree-master-0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ description = {

dependencies = {
"lua-resty-ipmatcher",
"lua-resty-expr = 1.0.0",
}

build = {
Expand Down
8 changes: 4 additions & 4 deletions t/vars.t
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ nil
location /t {
content_by_lua_block {
local radix = require("resty.radixtree")
local rx = radix.new({
local ok, err = pcall(radix.new, {
{
paths = "/aa",
metadata = "metadata /aa",
Expand All @@ -329,15 +329,15 @@ nil
}
})

ngx.say(rx:match("/aa", {vars = ngx.var}))
ngx.say(ok, " ", err)
}
}
--- request
GET /t?k=9
--- no_error_log
[error]
--- response_body
nil
--- response_body_like eval
qr/failed to handle expression: invalid operator 'invalid'/



Expand Down