From d89b485318c356feac528338e873224785e1254f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Thu, 3 Dec 2020 10:40:07 +0800 Subject: [PATCH] feat: refactor with lua-resty-expr (#77) --- README.md | 17 +-- lib/resty/radixtree.lua | 125 ++++-------------- .../lua-resty-radixtree-master-0-0.rockspec | 1 + t/vars.t | 8 +- 4 files changed, 33 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 3f464723..e8a37cc5 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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.|| @@ -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)` diff --git a/lib/resty/radixtree.lua b/lib/resty/radixtree.lua index cb41d37e..d89d199f 100644 --- a/lib/resty/radixtree.lua +++ b/lib/resty/radixtree.lua @@ -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 @@ -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 @@ -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 } @@ -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 @@ -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 = {} @@ -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 @@ -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) @@ -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 diff --git a/rockspec/lua-resty-radixtree-master-0-0.rockspec b/rockspec/lua-resty-radixtree-master-0-0.rockspec index 71ec175e..b1bd2991 100644 --- a/rockspec/lua-resty-radixtree-master-0-0.rockspec +++ b/rockspec/lua-resty-radixtree-master-0-0.rockspec @@ -14,6 +14,7 @@ description = { dependencies = { "lua-resty-ipmatcher", + "lua-resty-expr = 1.0.0", } build = { diff --git a/t/vars.t b/t/vars.t index 8fcd42c7..8e5a7a85 100644 --- a/t/vars.t +++ b/t/vars.t @@ -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", @@ -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'/