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: Custom parameter/segment regex #141

Closed
wants to merge 1 commit into from
Closed
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,21 @@ local rx = radix.new({
})
```

### Parameters in Path with a custom regex

You can specify parameters on a path and use a regex to match the parameter segment of the path (see `hsegment` of [RFC1738](https://www.rfc-editor.org/rfc/rfc1738.txt).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need this feature? Are there any other product that supports similar feature?

I took a look at https://www.rfc-editor.org/rfc/rfc1738.txt, I do not sure if we need to support Parameters in Path with a custom regex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reference is referring to radixtree.lua Line 655 and that the custom regex is for the segment of the uri (aka everything between a /).

In regards to having this type of validation/feature I know of prefix_regex in ambassador, there is also envoy route regex.

However, I think we could also use the var.request_uri in vars to get the feature. This in turn means having a regex for 1) matching the route 2) matching the route with the argument, from my perspective this go's against the performance that APISIX seems to talk about then again I maybe wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new feature for lua-resty-radixtree, I need more time to research and think. I'll reply you before Friday in this week

If you were hurry about this new feature, feel free to ping me ^_^

It is not possible to match multiple path segements in this way.

```lua
local rx = radix.new({
{
-- matches with `/user/john` but not `/user/` or `/user`
paths = {"/user/:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"}, -- for `/user/5b3c7845-b45c-4dc8-8843-0349465e0e62`, `opts.matched.user` will be `5b3c7845-b45c-4dc8-8843-0349465e0e62`
metadata = "metadata /user",
}
})
```

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

# Installation
Expand Down
49 changes: 32 additions & 17 deletions lib/resty/radixtree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ end
local function fetch_pat(path)
local pat = lru_pat:get(path)
if pat then
return pat[1], pat[2] -- pat, names
return pat -- pat
end

clear_tab(tmp)
Expand All @@ -646,28 +646,39 @@ local function fetch_pat(path)
return false
end

local names = {}
local name, pos, regex
for i, item in ipairs(res) do
local first_byte = item:byte(1, 1)
if first_byte == string.byte(":") then
table.insert(names, res[i]:sub(2))
-- See https://www.rfc-editor.org/rfc/rfc1738.txt BNF for specific URL schemes
res[i] = [=[([\w\-_;:@&=!',\%\$\.\+\*\(\)]+)]=]
pos = str_find(res[i], ":", 3, true)
if pos and pos+1 < res[i]:len() then
name = res[i]:sub(2, pos - 1)
regex = res[i]:sub(pos+1)
else
name = res[i]:sub(2)
-- See https://www.rfc-editor.org/rfc/rfc1738.txt BNF for specific URL schemes
regex = [=[[\w\-_;:@&=!',\%\$\.\+\*\(\)]+]=]
end

res[i] = '(?<' .. name .. '>' .. regex .. ')'
elseif first_byte == string.byte("*") then
local name = res[i]:sub(2)
name = res[i]:sub(2)
if name == "" then
name = ":ext"
name = "__ext"
end
table.insert(names, name)

-- '.' matches any character except newline
res[i] = [=[((.|\n)*)]=]
res[i] = '(?<' .. name .. '>' .. [=[(?:.|\n)*)]=]
end
end

if name == nil then
return false
end

pat = table.concat(res, [[\/]])
lru_pat:set(path, {pat, names}, 60 * 60)
return pat, names
lru_pat:set(path, pat, 60 * 60)
return pat
end


Expand All @@ -676,9 +687,9 @@ local function compare_param(req_path, route, opts)
return true
end

local pat, names = fetch_pat(route.path_org)
local pat = fetch_pat(route.path_org)
log_debug("pcre pat: ", pat)
if #names == 0 then
if pat == false then
return true
end

Expand All @@ -695,10 +706,14 @@ local function compare_param(req_path, route, opts)
return true
end

for i, v in ipairs(m) do
local name = names[i]
if name and v then
opts.matched[name] = v
for k, v in pairs(m) do
if type(k) == "string" and v then
-- Capture groups can't start with a colon.
if k == "__ext" then
opts.matched[":ext"] = v
else
opts.matched[k] = v
end
end
end
return true
Expand Down
36 changes: 36 additions & 0 deletions t/parameter.t
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,39 @@ GET /t
--- response_body
match meta: /user/:user
match meta: /user/:user/age/:age



=== TEST 14: /user/:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local radix = require("resty.radixtree")
local rx = radix.new({
{
paths = { "/user/:uuid:([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" },
metadata = "metadata /name",
},
})

local opts = {matched = {}}
local meta = rx:match("/user/5b3c7845-b45c-4dc8-8843-0349465e0e62", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))

opts.matched = {}
meta = rx:match("/user/1", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
match meta: metadata /name
matched: {"_path":"/user/:uuid:([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})","uuid":"5b3c7845-b45c-4dc8-8843-0349465e0e62"}
match meta: nil
matched: []
Loading