diff --git a/README.md b/README.md index af3ced3..f6bff35 100644 --- a/README.md +++ b/README.md @@ -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). +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 diff --git a/lib/resty/radixtree.lua b/lib/resty/radixtree.lua index 5f9bfad..7ef176d 100644 --- a/lib/resty/radixtree.lua +++ b/lib/resty/radixtree.lua @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/t/parameter.t b/t/parameter.t index 22ffa71..b352755 100644 --- a/t/parameter.t +++ b/t/parameter.t @@ -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: []