Skip to content

Commit

Permalink
support http.path.segments.* field
Browse files Browse the repository at this point in the history
comments clean

test cases

support http.path.segments.*

changelog

http.path.segments.rev.*

changelog

remove segments.rev.*

support range segments definition

lint fix

clean

string.buffer

clean

clean

cache segment result

remove #only tag

re_split ctx.pos=2

dont use re.match

clean

test cases

clean

more tests

apply suggestions

fix rebase issue
  • Loading branch information
chronolaw committed Jan 12, 2024
1 parent ce12f68 commit 001267a
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 1 deletion.
@@ -0,0 +1,4 @@
message: |
support `http.path.segments.*` field in expressions router flavor.
type: feature
scope: Core
1 change: 1 addition & 0 deletions kong/router/atc.lua
Expand Up @@ -62,6 +62,7 @@ do
["String"] = {"net.protocol", "tls.sni",
"http.method", "http.host",
"http.path",
"http.path.segments.*",
"http.headers.*",
"http.queries.*",
},
Expand Down
71 changes: 70 additions & 1 deletion kong/router/fields.lua
Expand Up @@ -167,6 +167,15 @@ end -- is_http
if is_http then

local fmt = string.format
local ngx_null = ngx.null
local re_split = require("ngx.re").split


local HTTP_SEGMENTS_PREFIX = "http.path.segments."
local HTTP_SEGMENTS_PREFIX_LEN = #HTTP_SEGMENTS_PREFIX
local HTTP_SEGMENTS_REG_CTX = { pos = 2, } -- skip first '/'
local HTTP_SEGMENTS_OFFSET = 1


-- func => get_headers or get_uri_args
-- name => "headers" or "queries"
Expand Down Expand Up @@ -209,7 +218,67 @@ if is_http then

return params.queries[field:sub(PREFIX_LEN + 1)]
end
end

elseif field:sub(1, HTTP_SEGMENTS_PREFIX_LEN) == HTTP_SEGMENTS_PREFIX then
return function(params)
if not params.segments then
HTTP_SEGMENTS_REG_CTX.pos = 2 -- reset ctx, skip first '/'
params.segments = re_split(params.uri, "/", "jo", HTTP_SEGMENTS_REG_CTX)
end

local segments = params.segments

local name = field:sub(HTTP_SEGMENTS_PREFIX_LEN + 1)
local value = segments[name]

if value then
return value ~= ngx_null and value or nil
end

-- "/a/b/c" => 1="a", 2="b", 3="c"
-- http.path.segments.0 => params.segments[1 + 0] => a
-- http.path.segments.1_2 => b/c

local p = name:find("_", 1, true)

-- only one segment

if not p then
local pos = tonumber(name)

value = pos and segments[HTTP_SEGMENTS_OFFSET + pos] or nil
segments[name] = value or ngx_null

return value
end

-- (pos1, pos2) defines a segment range

local pos1 = tonumber(name:sub(1, p - 1))
local pos2 = tonumber(name:sub(p + 1))
local segs_count = #segments - HTTP_SEGMENTS_OFFSET

if not pos1 or not pos2 or
pos1 >= pos2 or pos1 > segs_count or pos2 > segs_count
then
segments[name] = ngx_null
return nil
end

local buf = buffer.new()

for p = pos1, pos2 - 1 do
buf:put(segments[HTTP_SEGMENTS_OFFSET + p], "/")
end
buf:put(segments[HTTP_SEGMENTS_OFFSET + pos2])

value = buf:get()
segments[name] = value

return value
end

end -- if prefix

-- others return nil
end
Expand Down
166 changes: 166 additions & 0 deletions spec/01-unit/08-router_spec.lua
Expand Up @@ -5393,5 +5393,171 @@ do
assert.same(ctx.route_match_cached, "pos")
end)
end)

describe("Router (flavor = " .. flavor .. ") [http]", function()
reload_router(flavor)

it("select() should match single http.segments.*", function()
local use_case = {
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
expression = [[http.path.segments.0 == "foo" && http.path.segments.1 == "bar"]],
priority = 100,
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
expression = [[http.path.segments.0 == "foo" && http.path.segments.2 ^= "baz"]],
priority = 200,
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
expression = [[http.path.segments.0 == "foo" && http.path.segments.3 ~ r#"\d+"#]],
priority = 300,
},
},
}

local router = assert(new_router(use_case))

local match_t = router:select("GET", "/foo/bar")
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)

local match_t = router:select("GET", "/foo/bar/bazxxx")
assert.truthy(match_t)
assert.same(use_case[2].route, match_t.route)

local match_t = router:select("GET", "/foo/bar/baz/12345")
assert.truthy(match_t)
assert.same(use_case[3].route, match_t.route)

local match_t = router:select("GET", "/foo/xxx")
assert.falsy(match_t)
end)

it("select() should match range http.segments.*", function()
local use_case = {
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
expression = [[http.path.segments.0_1 ~ r#"\d+/\w+"#]],
priority = 100,
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
expression = [[http.path.segments.1_3 == r#"xxx/yyy/zzz"#]],
priority = 100,
},
},
}

local router = assert(new_router(use_case))

local match_t = router:select("GET", "/123/foo/bar")
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)

local match_t = router:select("GET", "/123/hello-world/bar")
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)

local match_t = router:select("GET", "/foo/xxx/yyy/zzz/bar")
assert.truthy(match_t)
assert.same(use_case[2].route, match_t.route)
end)

it("select() accepts but does not match invalid http.segments.*", function()
local use_case = {
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
expression = [[http.path.segments.a == r#"foo"#]],
priority = 100,
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
expression = [[http.path.segments.10_11 == r#"foo/bar"#]],
priority = 100,
},
},
}

local router = assert(new_router(use_case))

local match_t = router:select("GET", "/foo/bar")
assert.falsy(match_t)
end)

it("exec() should hit cache with http.segments.*", function()
local use_case = {
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
expression = [[http.path.segments.0 == "foo" && http.path.segments.1 == "bar"]],
priority = 100,
},
},
{
service = service,
route = {
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
expression = [[http.path.segments.1_3 == r#"xxx/yyy/zzz"#]],
priority = 100,
},
},
}

local router = assert(new_router(use_case))

local ctx = {}
local _ngx = mock_ngx("GET", "/foo/bar", { a = "1", })
router._set_ngx(_ngx)

-- first match
local match_t = router:exec(ctx)
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)
assert.falsy(ctx.route_match_cached)

-- cache hit pos
local match_t = router:exec(ctx)
assert.truthy(match_t)
assert.same(use_case[1].route, match_t.route)
assert.same(ctx.route_match_cached, "pos")

local ctx = {}
local _ngx = mock_ngx("GET", "/foo/xxx/yyy/zzz/bar", { a = "1", })
router._set_ngx(_ngx)

-- first match
local match_t = router:exec(ctx)
assert.truthy(match_t)
assert.same(use_case[2].route, match_t.route)
assert.falsy(ctx.route_match_cached)

-- cache hit pos
local match_t = router:exec(ctx)
assert.truthy(match_t)
assert.same(use_case[2].route, match_t.route)
assert.same(ctx.route_match_cached, "pos")
end)
end)
end -- local flavor = "expressions"

0 comments on commit 001267a

Please sign in to comment.