5929 lines
196 KiB
Lua
5929 lines
196 KiB
Lua
local Router
|
|
local path_handling_tests = require "spec.fixtures.router_path_handling_tests"
|
|
local uuid = require("kong.tools.uuid").uuid
|
|
local get_expression = require("kong.router.transform").get_expression
|
|
local deep_copy = require("kong.tools.table").deep_copy
|
|
|
|
local function reload_router(flavor, subsystem)
|
|
_G.kong = {
|
|
configuration = {
|
|
router_flavor = flavor,
|
|
},
|
|
}
|
|
|
|
ngx.config.subsystem = subsystem or "http" -- luacheck: ignore
|
|
|
|
package.loaded["kong.router.fields"] = nil
|
|
package.loaded["kong.router.atc"] = nil
|
|
package.loaded["kong.router.compat"] = nil
|
|
package.loaded["kong.router.expressions"] = nil
|
|
package.loaded["kong.router"] = nil
|
|
|
|
Router = require "kong.router"
|
|
end
|
|
|
|
local function new_router(cases, old_router)
|
|
return Router.new(cases, nil, nil, old_router)
|
|
end
|
|
|
|
local service = {
|
|
name = "service-invalid",
|
|
protocol = "http",
|
|
}
|
|
|
|
local headers_mt = {
|
|
__index = function(t, k)
|
|
local u = rawget(t, string.upper(k))
|
|
if u then
|
|
return u
|
|
end
|
|
|
|
return rawget(t, string.lower(k))
|
|
end
|
|
}
|
|
|
|
local spy_stub = {
|
|
nop = function() end
|
|
}
|
|
|
|
local function mock_ngx(method, request_uri, headers, queries, vars)
|
|
method = method or "GET"
|
|
request_uri = request_uri or "/"
|
|
headers = headers or {}
|
|
queries = queries or {}
|
|
vars = vars or {}
|
|
local _ngx
|
|
_ngx = {
|
|
log = ngx.log,
|
|
re = ngx.re,
|
|
var = setmetatable({
|
|
request_uri = request_uri,
|
|
http_kong_debug = headers.kong_debug
|
|
}, {
|
|
__index = function(_, key)
|
|
if key:sub(1, 5) == "http_" then
|
|
spy_stub.nop()
|
|
return headers[key:sub(6)] or vars[key]
|
|
end
|
|
return vars[key]
|
|
end,
|
|
}),
|
|
req = {
|
|
get_method = function()
|
|
return method
|
|
end,
|
|
get_headers = function()
|
|
return setmetatable(headers, headers_mt)
|
|
end,
|
|
get_uri_args = function()
|
|
return queries
|
|
end,
|
|
},
|
|
}
|
|
|
|
return _ngx
|
|
end
|
|
|
|
for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
|
|
describe("Router (flavor = " .. flavor .. ")", function()
|
|
reload_router(flavor)
|
|
local it_trad_only = (flavor == "traditional") and it or pending
|
|
|
|
describe("split_port()", function()
|
|
local split_port = require("kong.router.traditional").split_port
|
|
|
|
it("splits port number", function()
|
|
for _, case in ipairs({
|
|
{ { "" }, { "", "", false } },
|
|
{ { "localhost" }, { "localhost", "localhost", false } },
|
|
{ { "localhost:" }, { "localhost", "localhost", false } },
|
|
{ { "localhost:80" }, { "localhost", "localhost:80", true } },
|
|
{ { "localhost:23h" }, { "localhost:23h", "localhost:23h", false } },
|
|
{ { "localhost/24" }, { "localhost/24", "localhost/24", false } },
|
|
{ { "::1" }, { "::1", "::1", false } },
|
|
{ { "[::1]" }, { "::1", "[::1]", false } },
|
|
{ { "[::1]:" }, { "::1", "[::1]:", false } },
|
|
{ { "[::1]:80" }, { "::1", "[::1]:80", true } },
|
|
{ { "[::1]:80b" }, { "[::1]:80b", "[::1]:80b", false } },
|
|
{ { "[::1]/96" }, { "[::1]/96", "[::1]/96", false } },
|
|
|
|
{ { "", 88 }, { "", ":88", false } },
|
|
{ { "localhost", 88 }, { "localhost", "localhost:88", false } },
|
|
{ { "localhost:", 88 }, { "localhost", "localhost:88", false } },
|
|
{ { "localhost:80", 88 }, { "localhost", "localhost:80", true } },
|
|
{ { "localhost:23h", 88 }, { "localhost:23h", "[localhost:23h]:88", false } },
|
|
{ { "localhost/24", 88 }, { "localhost/24", "localhost/24:88", false } },
|
|
{ { "::1", 88 }, { "::1", "[::1]:88", false } },
|
|
{ { "[::1]", 88 }, { "::1", "[::1]:88", false } },
|
|
{ { "[::1]:", 88 }, { "::1", "[::1]:88", false } },
|
|
{ { "[::1]:80", 88 }, { "::1", "[::1]:80", true } },
|
|
{ { "[::1]:80b", 88 }, { "[::1]:80b", "[::1]:80b:88", false } },
|
|
{ { "[::1]/96", 88 }, { "[::1]/96", "[::1]/96:88", false } },
|
|
}) do
|
|
assert.same(case[2], { split_port(unpack(case[1])) })
|
|
end
|
|
end)
|
|
end)
|
|
|
|
describe("new()", function()
|
|
describe("[errors]", function()
|
|
it("enforces args types", function()
|
|
assert.error_matches(function()
|
|
Router.new()
|
|
end, "expected arg #1 routes to be a table", nil, true)
|
|
end)
|
|
|
|
it("enforces routes fields types", function()
|
|
local router, err = Router.new {
|
|
{
|
|
route = {
|
|
},
|
|
service = {
|
|
name = "service-invalid"
|
|
},
|
|
},
|
|
}
|
|
|
|
assert.is_nil(router)
|
|
assert.equal("could not categorize route", err)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("select()", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
|
|
-- 1. host
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
},
|
|
},
|
|
-- 2. method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
methods = {
|
|
"TRACE"
|
|
},
|
|
}
|
|
},
|
|
-- 3. uri
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
paths = {
|
|
"/my-route"
|
|
},
|
|
}
|
|
},
|
|
-- 4. host + uri
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
paths = {
|
|
"/route-4"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
},
|
|
},
|
|
-- 5. host + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8105",
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH"
|
|
},
|
|
},
|
|
},
|
|
-- 6. uri + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8106",
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH",
|
|
},
|
|
paths = {
|
|
"/route-6"
|
|
},
|
|
}
|
|
},
|
|
-- 7. host + uri + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8107",
|
|
hosts = {
|
|
"domain-with-uri-1.org",
|
|
"domain-with-uri-2.org"
|
|
},
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH",
|
|
},
|
|
paths = {
|
|
"/my-route-uri"
|
|
},
|
|
},
|
|
},
|
|
-- 8. serviceless-route
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8108",
|
|
paths = {
|
|
"/serviceless"
|
|
},
|
|
}
|
|
},
|
|
-- 9. headers (single)
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8109",
|
|
headers = {
|
|
location = {
|
|
"my-location-1",
|
|
"my-location-2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
-- 10. headers (multiple)
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8110",
|
|
headers = {
|
|
location = {
|
|
"my-location-1",
|
|
},
|
|
version = {
|
|
"v1",
|
|
"v2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
-- 11. headers + uri
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8111",
|
|
headers = {
|
|
location = {
|
|
"my-location-1",
|
|
"my-location-2",
|
|
},
|
|
},
|
|
paths = {
|
|
"/headers-uri"
|
|
},
|
|
},
|
|
},
|
|
-- 12. host + headers + uri + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8112",
|
|
hosts = {
|
|
"domain-with-headers-1.org",
|
|
"domain-with-headers-2.org"
|
|
},
|
|
headers = {
|
|
location = {
|
|
"my-location-1",
|
|
"my-location-2",
|
|
},
|
|
},
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH",
|
|
},
|
|
paths = {
|
|
"/headers-host-uri-method"
|
|
},
|
|
},
|
|
},
|
|
-- 13. host + port
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8113",
|
|
hosts = {
|
|
"domain-1.org:321",
|
|
"domain-2.org"
|
|
},
|
|
},
|
|
},
|
|
-- 14. no "any-port" route
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8114",
|
|
hosts = {
|
|
"domain-3.org:321",
|
|
},
|
|
},
|
|
},
|
|
-- 15. headers (regex)
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8115",
|
|
headers = {
|
|
user_agent = {
|
|
"~*windows|linux|os\\s+x\\s*[\\d\\._]+|solaris|bsd",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
router = assert(new_router(use_case))
|
|
|
|
-- let atc-router happy
|
|
local _ngx = mock_ngx("GET", "/", { a = "1" })
|
|
router._set_ngx(_ngx)
|
|
end)
|
|
|
|
|
|
it("[host]", function()
|
|
-- host
|
|
local match_t = router:select("GET", "/", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[1].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host] ignores default port", function()
|
|
-- host
|
|
local match_t = router:select("GET", "/", "domain-1.org:80")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[1].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it_trad_only("[host] weird port matches no-port route", function()
|
|
local match_t = router:select("GET", "/", "domain-1.org:123")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
assert.same(use_case[1].route.hosts[1], match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host] matches specific port", function()
|
|
-- host
|
|
local match_t = router:select("GET", "/", "domain-1.org:321")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[13].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[13].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host] matches specific port on port-only route", function()
|
|
-- host
|
|
local match_t = router:select("GET", "/", "domain-3.org:321")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[14].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[14].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host] fails just because of port on port-only route", function()
|
|
-- host
|
|
local match_t = router:select("GET", "/", "domain-3.org:123")
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
it("[uri]", function()
|
|
-- uri
|
|
local match_t = router:select("GET", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[3].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[uri + empty host]", function()
|
|
-- uri only (no Host)
|
|
-- Supported for HTTP/1.0 requests without a Host header
|
|
local match_t = router:select("GET", "/my-route-uri", "")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[3].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[method]", function()
|
|
-- method
|
|
local match_t = router:select("TRACE", "/", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[2].route.methods[1], match_t.matches.method)
|
|
end
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host + uri]", function()
|
|
-- host + uri
|
|
local match_t = router:select("GET", "/route-4", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[4].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[4].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host + method]", function()
|
|
-- host + method
|
|
local match_t = router:select("POST", "/", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[5].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[5].route.hosts[1], match_t.matches.host)
|
|
assert.same(use_case[5].route.methods[1], match_t.matches.method)
|
|
end
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[uri + method]", function()
|
|
-- uri + method
|
|
local match_t = router:select("PUT", "/route-6", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[6].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[6].route.methods[2], match_t.matches.method)
|
|
assert.same(use_case[6].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("[host + uri + method]", function()
|
|
-- uri + method
|
|
local match_t = router:select("PUT", "/my-route-uri",
|
|
"domain-with-uri-2.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[7].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[7].route.hosts[2], match_t.matches.host)
|
|
assert.same(use_case[7].route.methods[2], match_t.matches.method)
|
|
assert.same(use_case[7].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("single [headers] value", function()
|
|
-- headers (single)
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = "my-location-1"
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[9].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-1" }, match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = "my-location-2"
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[9].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-2" }, match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = { "my-location-3", "my-location-2" }
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[9].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-2" }, match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = "my-location-3"
|
|
})
|
|
assert.is_nil(match_t)
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = { "my-location-3", "foo" }
|
|
})
|
|
assert.is_nil(match_t)
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[15].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ user_agent = "mozilla/5.0 (x11; linux x86_64) applewebkit/537.36 (khtml, like gecko) chrome/83.0.4103.116 safari/537.36" }, match_t.matches.headers)
|
|
end
|
|
end)
|
|
|
|
it("multiple [headers] values", function()
|
|
-- headers (multiple)
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = "my-location-1",
|
|
version = "v1",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[10].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-1", version = "v1", },
|
|
match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = "my-location-1",
|
|
version = "v2",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[10].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-1", version = "v2", },
|
|
match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = { "my-location-3", "my-location-1" },
|
|
version = "v2",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[10].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-1", version = "v2", },
|
|
match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil, {
|
|
location = { "my-location-3", "my-location-2" },
|
|
version = "v2",
|
|
})
|
|
-- fallback to Route 9
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[9].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-2" }, match_t.matches.headers)
|
|
end
|
|
end)
|
|
|
|
it("[headers + uri]", function()
|
|
-- headers + uri
|
|
local match_t = router:select("GET", "/headers-uri", nil, "http", nil, nil, nil,
|
|
nil, nil, { location = "my-location-2" })
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[11].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[11].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same({ location = "my-location-2" }, match_t.matches.headers)
|
|
end
|
|
end)
|
|
|
|
it("[host + headers + uri + method]", function()
|
|
-- host + headers + uri + method
|
|
local match_t = router:select("PUT", "/headers-host-uri-method",
|
|
"domain-with-headers-1.org", "http",
|
|
nil, nil, nil, nil, nil, {
|
|
location = "my-location-2",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[12].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[12].route.hosts[1], match_t.matches.host)
|
|
assert.same(use_case[12].route.methods[2], match_t.matches.method)
|
|
assert.same(use_case[12].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[12].route.headers.location[2],
|
|
match_t.matches.headers.location)
|
|
end
|
|
end)
|
|
|
|
it("[serviceless]", function()
|
|
local match_t = router:select("GET", "/serviceless")
|
|
assert.truthy(match_t)
|
|
assert.is_nil(match_t.service)
|
|
assert.is_nil(match_t.matches.uri_captures)
|
|
assert.same(use_case[8].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[8].route.paths[1], match_t.matches.uri)
|
|
end
|
|
end)
|
|
|
|
if flavor == "traditional" then
|
|
describe("[IPv6 literal host]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- 1: no port, with and without brackets, unique IPs
|
|
{
|
|
service = service,
|
|
route = {
|
|
hosts = { "::11", "[::12]" },
|
|
},
|
|
},
|
|
|
|
-- 2: no port, with and without brackets, same hosts as 4
|
|
{
|
|
service = service,
|
|
route = {
|
|
hosts = { "::21", "[::22]" },
|
|
},
|
|
},
|
|
|
|
-- 3: unique IPs, with port
|
|
{
|
|
service = service,
|
|
route = {
|
|
hosts = { "[::31]:321", "[::32]:321" },
|
|
},
|
|
},
|
|
|
|
-- 4: same hosts as 2, with port, needs brackets
|
|
{
|
|
service = service,
|
|
route = {
|
|
hosts = { "[::21]:321", "[::22]:321" },
|
|
},
|
|
},
|
|
}
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
describe("no-port route is any-port", function()
|
|
describe("no-port request", function()
|
|
it("plain match", function()
|
|
local match_t = assert(router:select("GET", "/", "::11"))
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
it("with brackets", function()
|
|
local match_t = assert(router:select("GET", "/", "[::11]"))
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
it("explicit port still matches", function()
|
|
local match_t = assert(router:select("GET", "/", "[::11]:654"))
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("port-specific route", function()
|
|
it("matches by port", function()
|
|
local match_t = assert(router:select("GET", "/", "[::21]:321"))
|
|
assert.same(use_case[4].route, match_t.route)
|
|
|
|
local match_t = assert(router:select("GET", "/", "[::31]:321"))
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
it("matches other ports to any-port fallback", function()
|
|
local match_t = assert(router:select("GET", "/", "[::21]:654"))
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("fails if there's no any-port route", function()
|
|
local match_t = router:select("GET", "/", "[::31]:654")
|
|
assert.falsy(match_t)
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
describe("[uri prefix]", function()
|
|
it("matches when given [uri] is in request URI prefix", function()
|
|
-- uri prefix
|
|
local match_t = router:select("GET", "/my-route/some/path", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[3].route.paths[1], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("does not supersede another route with a longer [uri]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/my-route/hello" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/my-route/hello", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route/hello", match_t.matches.uri)
|
|
end
|
|
|
|
match_t = router:select("GET", "/my-route/hello/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route/hello", match_t.matches.uri)
|
|
end
|
|
|
|
match_t = router:select("GET", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route", match_t.matches.uri)
|
|
end
|
|
|
|
match_t = router:select("GET", "/my-route/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route", match_t.matches.uri)
|
|
end
|
|
end)
|
|
|
|
it("does not supersede another route with a longer [uri] while [methods] are also defined", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "POST", "PUT", "GET" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
methods = { "POST", "PUT", "GET" },
|
|
paths = { "/my-route/hello" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/my-route/hello", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route/hello/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("does not superseds another route with a longer [uri] while [hosts] are also defined", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-route/hello" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/my-route/hello", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route/hello/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("does not supersede another route with a longer [uri] when a better [uri] match exists for another [host]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "example.com" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "example.com" },
|
|
paths = { "/my-route/hello" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "example.net" },
|
|
paths = { "/my-route/hello/world" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/my-route/hello/world", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/my-route/hello/world/and/goodnight", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("only matches [uri prefix] as a prefix (anchored mode)", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/something/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "example.com" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/something/my-route", "example.com")
|
|
assert.truthy(match_t)
|
|
-- would be route-2 if URI matching was not prefix-only (anchored mode)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/something/my-route", match_t.matches.uri)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
describe("[uri regex]", function()
|
|
it("matches with [uri regex]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/\d+/profile]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/users/123/profile", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same([[/users/\d+/profile]], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches the right route when several ones have a [uri regex]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/route/persons/\d{3}]] },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { [[~/route/persons/\d{3}/following]] },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
paths = { [[~/route/persons/\d{3}/[a-z]+]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/route/persons/456", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("matches a [uri regex] even if a [prefix uri] got a match", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[/route/persons]] },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { [[~/route/persons/\d+/profile]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/route/persons/123/profile",
|
|
"domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same([[/route/persons/\d+/profile]], match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches a [uri regex] even if a [uri] got an exact match", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/route/fixture" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "~/route/(fixture)" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/route/fixture", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
if flavor == "traditional" then
|
|
assert.same("/route/(fixture)", match_t.matches.uri)
|
|
end
|
|
end)
|
|
|
|
it("matches a [uri regex + host] even if a [prefix uri] got a match", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "route.com" },
|
|
paths = { "/pat" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "route.com" },
|
|
paths = { "/path" },
|
|
methods = { "POST" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "route.com" },
|
|
paths = { "~/(path)" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/path", "route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("route.com", match_t.matches.host)
|
|
assert.same("/(path)", match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
end)
|
|
|
|
it("matches from the beginning of the request URI [uri regex]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/prefix/[0-9]+]] }
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
-- sanity
|
|
local match_t = router:select("GET", "/prefix/123", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
assert.same(nil, match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
|
|
match_t = router:select("GET", "/extra/prefix/123", "domain.org")
|
|
assert.is_nil(match_t)
|
|
end)
|
|
end)
|
|
|
|
describe("[wildcard host]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "*.route.com" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "route.*" },
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("matches leftmost wildcards", function()
|
|
local match_t = router:select("GET", "/", "foo.route.com", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(use_case[1].route.hosts[1], match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches rightmost wildcards", function()
|
|
local match_t = router:select("GET", "/", "route.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("matches any port in request", function()
|
|
local match_t = router:select("GET", "/", "route.org:123")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/", "foo.route.com:123", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("matches port-specific routes", function()
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "*.route.net:123" },
|
|
},
|
|
})
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
hosts = { "route.*:123" }, -- same as [2] but port-specific
|
|
},
|
|
})
|
|
router = assert(new_router(use_case))
|
|
|
|
finally(function()
|
|
table.remove(use_case)
|
|
table.remove(use_case)
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
-- match the right port
|
|
local match_t = router:select("GET", "/", "foo.route.net:123")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
-- fail different port
|
|
assert.is_nil(router:select("GET", "/", "foo.route.net:456"))
|
|
|
|
-- port-specific is higher priority
|
|
local match_t = router:select("GET", "/", "route.org:123")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
end)
|
|
|
|
it("prefers port-specific even for http default port", function()
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "route.*:80" }, -- same as [2] but port-specific
|
|
},
|
|
})
|
|
router = assert(new_router(use_case))
|
|
|
|
finally(function()
|
|
table.remove(use_case)
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
-- non-port matches any
|
|
local match_t = assert(router:select("GET", "/", "route.org:123"))
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
-- port 80 goes to port-specific route
|
|
local match_t = assert(router:select("GET", "/", "route.org:80"))
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
-- even if it's implicit port 80
|
|
if flavor == "traditional" then
|
|
local match_t = assert(router:select("GET", "/", "route.org"))
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end
|
|
end)
|
|
|
|
it("prefers port-specific even for https default port", function()
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "route.*:443" }, -- same as [2] but port-specific
|
|
},
|
|
})
|
|
router = assert(new_router(use_case))
|
|
|
|
finally(function()
|
|
table.remove(use_case)
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
-- non-port matches any
|
|
local match_t = assert(router:select("GET", "/", "route.org:123"))
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
-- port 443 goes to port-specific route
|
|
local match_t = assert(router:select("GET", "/", "route.org:443"))
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
-- even if it's implicit port 443
|
|
if flavor == "traditional" then
|
|
local match_t = assert(router:select("GET", "/", "route.org", "https"))
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end
|
|
end)
|
|
|
|
it("does not take precedence over a plain host", function()
|
|
table.insert(use_case, 1, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "plain.route.com" },
|
|
},
|
|
})
|
|
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
hosts = { "route.com" },
|
|
},
|
|
})
|
|
|
|
finally(function()
|
|
table.remove(use_case, 1)
|
|
table.remove(use_case)
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/", "route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("route.com", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
|
|
match_t = router:select("GET", "/", "route.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("route.*", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
|
|
match_t = router:select("GET", "/", "plain.route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("plain.route.com", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
|
|
match_t = router:select("GET", "/", "foo.route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("*.route.com", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches [wildcard host + path] even if a similar [plain host] exists", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "*.route.com" },
|
|
paths = { "/path1" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "plain.route.com" },
|
|
paths = { "/path2" },
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/path1", "plain.route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("*.route.com", match_t.matches.host)
|
|
assert.same("/path1", match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches [plain host + path] even if a matching [wildcard host] exists", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "*.route.com" },
|
|
paths = { "/path1" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "plain.route.com" },
|
|
paths = { "/path2" },
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/path2", "plain.route.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("plain.route.com", match_t.matches.host)
|
|
assert.same("/path2", match_t.matches.uri)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("submatch_weight [wildcard host port] > [wildcard host] ", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "route.*" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "route.*:80", "route.com.*" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/", "route.org:80")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("route.*:80", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("matches a [wildcard host + port] even if a [wildcard host] matched", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "route.*" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "route.*:123" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "route.*:80" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
-- explicit port
|
|
local match_t = router:select("GET", "/", "route.org:123")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("route.*:123", match_t.matches.host)
|
|
end
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
|
|
-- implicit port
|
|
if flavor == "traditional" then
|
|
local match_t = router:select("GET", "/", "route.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
assert.same("route.*:80", match_t.matches.host)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end
|
|
end)
|
|
|
|
it("matches [wildcard/plain + uri + method]", function()
|
|
finally(function()
|
|
table.remove(use_case)
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "*.domain.com", "example.com" },
|
|
paths = { "/path" },
|
|
methods = { "GET", "TRACE" },
|
|
},
|
|
})
|
|
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("POST", "/path", "foo.domain.com")
|
|
assert.is_nil(match_t)
|
|
|
|
match_t = router:select("GET", "/path", "foo.domain.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[#use_case].route, match_t.route)
|
|
|
|
match_t = router:select("TRACE", "/path", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[#use_case].route, match_t.route)
|
|
|
|
match_t = router:select("POST", "/path", "foo.domain.com")
|
|
assert.is_nil(match_t)
|
|
end)
|
|
end)
|
|
|
|
describe("[wildcard host] + [uri regex]", function()
|
|
it("matches", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "*.example.com" },
|
|
paths = { [[~/users/\d+/profile]] },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "*.example.com" },
|
|
paths = { [[/users]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/users/123/profile",
|
|
"test.example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/users", "test.example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("[headers]", function()
|
|
it("evaluates Routes with more [headers] first", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
headers = {
|
|
version = { "v1", "v2" },
|
|
user_agent = { "foo", "bar" },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
headers = {
|
|
version = { "v1", "v2" },
|
|
user_agent = { "foo", "bar" },
|
|
location = { "east", "west" },
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
{
|
|
version = "v1",
|
|
user_agent = "foo",
|
|
location = { "north", "west" },
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("names are case-insensitive", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
headers = {
|
|
["USER_AGENT"] = { "foo", "bar" },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
headers = {
|
|
user_agent = { "baz" },
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
setmetatable({
|
|
user_agent = "foo",
|
|
}, headers_mt))
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same({ user_agent = "foo" }, match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
setmetatable({
|
|
["USER_AGENT"] = "baz",
|
|
}, headers_mt))
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same({ user_agent = "baz" }, match_t.matches.headers)
|
|
end
|
|
end)
|
|
|
|
it("matches values in a case-insensitive way", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
headers = {
|
|
user_agent = { "foo", "bar" },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
headers = {
|
|
user_agent = { "BAZ" },
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
{
|
|
user_agent = "FOO",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same({ user_agent = "foo" }, match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
{
|
|
user_agent = "baz",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same({ user_agent = "baz" }, match_t.matches.headers)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
if flavor ~= "traditional" then
|
|
describe("[wildcard sni]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
snis = { "*.sni.test" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
snis = { "sni.*" },
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("matches leftmost wildcards", function()
|
|
for _, sni in ipairs({"foo.sni.test", "foo.bar.sni.test"}) do
|
|
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
|
|
sni)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
assert.same(nil, match_t.matches.method)
|
|
assert.same(nil, match_t.matches.uri)
|
|
assert.same(nil, match_t.matches.uri_captures)
|
|
end
|
|
end)
|
|
|
|
it("matches rightmost wildcards", function()
|
|
for _, sni in ipairs({"sni.foo", "sni.foo.bar"}) do
|
|
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
|
|
sni)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end
|
|
end)
|
|
|
|
it("doesn't match wildcard", function()
|
|
for _, sni in ipairs({"bar.sni.foo", "foo.sni.test.bar"}) do
|
|
local match_t = router:select("GET", "/", "any.test", "https", nil, nil, nil, nil,
|
|
sni)
|
|
assert.is_nil(match_t)
|
|
end
|
|
end)
|
|
end)
|
|
end -- if flavor ~= "traditional" then
|
|
|
|
|
|
if flavor ~= "traditional" then
|
|
describe("incremental rebuild", function()
|
|
local router
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/bar", },
|
|
updated_at = 90,
|
|
},
|
|
}
|
|
}
|
|
|
|
before_each(function()
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("matches initially", function()
|
|
local match_t = router:select("GET", "/foo")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/bar")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("update/remove works", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo1", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local nrouter = assert(new_router(use_case, router))
|
|
|
|
assert.equal(nrouter, router)
|
|
|
|
local match_t = nrouter:select("GET", "/foo1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = nrouter:select("GET", "/bar")
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
it("update with wrong route", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "~/delay/(?<delay>[^\\/]+)$", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local ok, nrouter = pcall(new_router, use_case, router)
|
|
|
|
assert(ok)
|
|
assert.equal(nrouter, router)
|
|
assert.equal(#nrouter.routes, 0)
|
|
end)
|
|
|
|
it("update skips routes if updated_at is unchanged", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/baz", },
|
|
updated_at = 90,
|
|
},
|
|
}
|
|
}
|
|
|
|
local nrouter = assert(new_router(use_case, router))
|
|
|
|
assert.equal(nrouter, router)
|
|
|
|
local match_t = nrouter:select("GET", "/baz")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = nrouter:select("GET", "/bar")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("clears match and negative cache after rebuild", function()
|
|
local match_t = router:select("GET", "/baz")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select("GET", "/foo")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foz", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/baz", },
|
|
updated_at = 100,
|
|
},
|
|
}
|
|
}
|
|
|
|
local nrouter = assert(new_router(use_case, router))
|
|
|
|
assert.equal(nrouter, router)
|
|
|
|
local match_t = nrouter:select("GET", "/foo")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = nrouter:select("GET", "/baz")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("detects concurrent incremental builds", function()
|
|
local use_cases = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foz", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/baz", },
|
|
updated_at = 100,
|
|
},
|
|
}
|
|
}
|
|
|
|
-- needs to be larger than YIELD_ITERATIONS
|
|
for i = 1, 2000 do
|
|
use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb" .. string.format("%04d", i),
|
|
paths = { "/" .. i, },
|
|
updated_at = 100,
|
|
},
|
|
}
|
|
end
|
|
|
|
local threads = {}
|
|
|
|
-- make sure yield() actually works
|
|
ngx.IS_CLI = false
|
|
|
|
for i = 1, 10 do
|
|
threads[i] = ngx.thread.spawn(function()
|
|
return new_router(use_cases, router)
|
|
end)
|
|
end
|
|
|
|
local error_detected = false
|
|
|
|
for i = 1, 10 do
|
|
local _, _, err = ngx.thread.wait(threads[i])
|
|
if err == "concurrent incremental router rebuild without mutex, this is unsafe" then
|
|
error_detected = true
|
|
break
|
|
end
|
|
end
|
|
|
|
ngx.IS_CLI = true
|
|
|
|
assert.truthy(error_detected)
|
|
end)
|
|
|
|
it("generates the correct diff", function()
|
|
local old_router = assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/a"
|
|
},
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}))
|
|
|
|
local add_matcher = spy.on(old_router.router, "add_matcher")
|
|
local remove_matcher = spy.on(old_router.router, "remove_matcher")
|
|
|
|
assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/b",
|
|
},
|
|
updated_at = 101,
|
|
}
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/c",
|
|
},
|
|
updated_at = 102,
|
|
},
|
|
},
|
|
}, old_router))
|
|
|
|
assert.spy(add_matcher).was_called(2)
|
|
assert.spy(remove_matcher).was_called(1)
|
|
end)
|
|
|
|
it("remove the correct diff", function()
|
|
local old_router = assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/a"
|
|
},
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/b",
|
|
},
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}))
|
|
|
|
local add_matcher = spy.on(old_router.router, "add_matcher")
|
|
local remove_matcher = spy.on(old_router.router, "remove_matcher")
|
|
|
|
assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/a",
|
|
},
|
|
updated_at = 100,
|
|
}
|
|
},
|
|
}, old_router))
|
|
|
|
assert.spy(add_matcher).was_called(1)
|
|
assert.spy(remove_matcher).was_called(2)
|
|
end)
|
|
|
|
it("update the correct diff: one route", function()
|
|
local old_router = assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/a"
|
|
},
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/b",
|
|
},
|
|
updated_at = 90,
|
|
},
|
|
},
|
|
}))
|
|
|
|
local add_matcher = spy.on(old_router.router, "add_matcher")
|
|
local remove_matcher = spy.on(old_router.router, "remove_matcher")
|
|
|
|
assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/aa",
|
|
},
|
|
updated_at = 101,
|
|
}
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/b",
|
|
},
|
|
updated_at = 90,
|
|
},
|
|
},
|
|
}, old_router))
|
|
|
|
assert.spy(add_matcher).was_called(1)
|
|
assert.spy(remove_matcher).was_called(1)
|
|
end)
|
|
|
|
it("update the correct diff: two routes", function()
|
|
local old_router = assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/a"
|
|
},
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/b",
|
|
},
|
|
updated_at = 90,
|
|
},
|
|
},
|
|
}))
|
|
|
|
local add_matcher = spy.on(old_router.router, "add_matcher")
|
|
local remove_matcher = spy.on(old_router.router, "remove_matcher")
|
|
|
|
assert(new_router({
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/aa",
|
|
},
|
|
updated_at = 101,
|
|
}
|
|
},
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/bb",
|
|
},
|
|
updated_at = 91,
|
|
},
|
|
},
|
|
}, old_router))
|
|
|
|
assert.spy(add_matcher).was_called(2)
|
|
assert.spy(remove_matcher).was_called(2)
|
|
end)
|
|
|
|
it("works well if route has no proper id", function()
|
|
local wrong_use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = nil, -- no id here
|
|
paths = { "/foo", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local nrouter = new_router(wrong_use_case, router)
|
|
assert.is_nil(nrouter)
|
|
assert.falsy(router.rebuilding)
|
|
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo1", },
|
|
updated_at = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local nrouter = assert(new_router(use_case, router))
|
|
|
|
assert.equal(nrouter, router)
|
|
assert.falsy(router.rebuilding)
|
|
|
|
local match_t = nrouter:select("GET", "/foo1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = nrouter:select("GET", "/bar")
|
|
assert.falsy(match_t)
|
|
end)
|
|
end)
|
|
|
|
describe("check empty route fields", function()
|
|
local use_case
|
|
|
|
before_each(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
paths = { "/foo", },
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
local empty_values = { {}, ngx.null, nil }
|
|
for i = 1, 3 do
|
|
local v = empty_values[i]
|
|
|
|
it("empty methods", function()
|
|
use_case[1].route.methods = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(http.path ^= r#"/foo"#)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty hosts", function()
|
|
use_case[1].route.hosts = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty headers", function()
|
|
use_case[1].route.headers = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty paths", function()
|
|
use_case[1].route.paths = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty snis", function()
|
|
use_case[1].route.snis = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
end
|
|
end)
|
|
|
|
describe("raw string", function()
|
|
local use_case
|
|
|
|
before_each(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
it("path has '\"'", function()
|
|
use_case[1].route.paths = { [[~/\"/*$]], }
|
|
|
|
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\"/*$"#)]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("path has '\"#'", function()
|
|
use_case[1].route.paths = { [[~/\"#/*$]], }
|
|
|
|
assert.equal([[(http.method == r#"GET"#) && (http.path ~ "^/\\\"#/*$")]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
end)
|
|
|
|
describe("check regex with '\\'", function()
|
|
local use_case
|
|
|
|
before_each(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
it("regex path has double '\\'", function()
|
|
use_case[1].route.paths = { [[~/\\/*$]], }
|
|
|
|
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\\/*$"#)]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("regex path has '\\d'", function()
|
|
use_case[1].route.paths = { [[~/\d+]], }
|
|
|
|
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\d+"#)]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
end)
|
|
|
|
describe("generate http expression", function()
|
|
local use_case
|
|
|
|
before_each(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
it("should always add lower() when matching http.headers.*", function()
|
|
use_case[1].route.methods = { "GET" }
|
|
use_case[1].route.headers = { test = { "~*Quote" }, }
|
|
|
|
assert.equal([[(http.method == r#"GET"#) && (any(lower(http.headers.test)) ~ r#"quote"#)]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("should use 'net.dst.port' when deprecating 'net.port'", function()
|
|
use_case[1].route.hosts = { "www.example.com:8000" }
|
|
|
|
assert.equal([[((http.host == r#"www.example.com"# && net.dst.port == 8000))]],
|
|
get_expression(use_case[1].route))
|
|
assert(new_router(use_case))
|
|
end)
|
|
end)
|
|
end -- if flavor ~= "traditional"
|
|
|
|
describe("normalization stopgap measurements", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- plain
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8100",
|
|
paths = {
|
|
"/plain/a.b.c", -- /plain/a.b.c
|
|
},
|
|
},
|
|
},
|
|
-- percent encoding with unreserved char, route should not be normalized
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/plain/a.b%58c", -- /plain/a.bXc
|
|
},
|
|
},
|
|
},
|
|
-- regex. It is no longer normalized since 3.0
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"~/reg%65x/\\d+", -- /regex/\d+
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
paths = {
|
|
"~/regex-meta/%5Cd\\+%2E", -- /regex-meta/\d\+.
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
paths = {
|
|
"~/regex-reserved%2Fabc", -- /regex-reserved/abc
|
|
},
|
|
},
|
|
},
|
|
}
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("matches against plain text paths", function()
|
|
local match_t = router:select("GET", "/plain/a.b.c", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
-- route no longer normalize user configured path
|
|
match_t = router:select("GET", "/plain/a.bXc", "example.com")
|
|
assert.falsy(match_t)
|
|
match_t = router:select("GET", "/plain/a.b%58c", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/plain/aab.c", "example.com")
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
it("matches against regex paths", function()
|
|
local match_t = router:select("GET", "/regex/123", "example.com")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select("GET", "/reg%65x/123", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/regex/\\d+", "example.com")
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
it("escapes meta character after percent decoding from regex paths", function()
|
|
local match_t = router:select("GET", "/regex-meta/123a", "example.com")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select("GET", "/regex-meta/\\d+.", "example.com")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select("GET", "/regex-meta/%5Cd+%2E", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
end)
|
|
|
|
it("leave reserved characters alone in regex paths", function()
|
|
local match_t = router:select("GET", "/regex-reserved/abc", "example.com")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select("GET", "/regex-reserved%2Fabc", "example.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[5].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("edge-cases", function()
|
|
it("[host] and [uri] have higher priority than [method]", function()
|
|
local use_case = {
|
|
-- 1. host
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
},
|
|
},
|
|
-- 2. method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
methods = {
|
|
"TRACE"
|
|
},
|
|
}
|
|
},
|
|
-- 3. uri
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
paths = {
|
|
"/my-route"
|
|
},
|
|
}
|
|
},
|
|
-- 4. host + uri
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
paths = {
|
|
"/route-4"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
},
|
|
},
|
|
-- 5. host + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8105",
|
|
hosts = {
|
|
"domain-1.org",
|
|
"domain-2.org"
|
|
},
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH"
|
|
},
|
|
},
|
|
},
|
|
-- 6. uri + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8106",
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH",
|
|
},
|
|
paths = {
|
|
"/route-6"
|
|
},
|
|
}
|
|
},
|
|
-- 7. host + uri + method
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8107",
|
|
hosts = {
|
|
"domain-with-uri-1.org",
|
|
"domain-with-uri-2.org"
|
|
},
|
|
methods = {
|
|
"POST",
|
|
"PUT",
|
|
"PATCH",
|
|
},
|
|
paths = {
|
|
"/my-route-uri"
|
|
},
|
|
},
|
|
},
|
|
}
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("TRACE", "/", "domain-2.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
-- uri
|
|
local match_t = router:select("TRACE", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
it("half [uri] and [host] match does not supersede another route", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "host1.com" },
|
|
paths = { "/v1/path" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "host2.com" },
|
|
paths = { "/" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/v1/path", "host1.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/v1/path", "host2.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("half [wildcard host] and [method] match does not supersede another route", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "host.*" },
|
|
methods = { "GET" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "host.*" },
|
|
methods = { "POST" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/", "host.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("POST", "/", "host.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("half [uri regex] and [method] match does not supersede another route", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
paths = { [[~/users/\d+/profile]] },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
methods = { "POST" },
|
|
paths = { [[~/users/\d*/profile]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/users/123/profile", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("POST", "/users/123/profile", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("[method] does not supersede [uri prefix]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/example" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/example", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/example/status/200", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("[method] does not supersede [wildcard host]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
methods = { "GET" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "domain.*" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/", "nothing.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/", "domain.com")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it_trad_only("does not supersede another route with a longer [uri prefix]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/a", "/bbbbbbb" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/a/bb" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/a/bb/foobar", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
describe("root / [uri]", function()
|
|
lazy_setup(function()
|
|
table.insert(use_case, 1, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb810f",
|
|
paths = { "/" },
|
|
}
|
|
})
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
table.remove(use_case, 1)
|
|
end)
|
|
|
|
it("request with [method]", function()
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("does not supersede another route", function()
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/my-route", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
|
|
match_t = router:select("GET", "/my-route/hello/world", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
end)
|
|
|
|
it("acts as a catch-all route", function()
|
|
local router = assert(new_router(use_case))
|
|
local match_t = router:select("GET", "/foobar/baz", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("multiple routes of same category with conflicting values", function()
|
|
-- reload router to reset combined cached matchers
|
|
reload_router(flavor)
|
|
|
|
local n = 6
|
|
|
|
lazy_setup(function()
|
|
-- all those routes are of the same category:
|
|
-- [host + uri]
|
|
for i = 1, n - 1 do
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb811" .. i,
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-uri" },
|
|
},
|
|
})
|
|
end
|
|
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8121",
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-target-uri" },
|
|
},
|
|
})
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
for _ = 1, n do
|
|
table.remove(use_case)
|
|
end
|
|
end)
|
|
|
|
it("matches correct route", function()
|
|
local router = assert(new_router(use_case))
|
|
|
|
-- let atc-router happy
|
|
local _ngx = mock_ngx("GET", "/", { a = "1" })
|
|
router._set_ngx(_ngx)
|
|
|
|
local match_t = router:select("GET", "/my-target-uri", "domain.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[#use_case].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
it("more [headers] has priority over longer [paths]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
headers = {
|
|
version = { "v1" },
|
|
},
|
|
paths = { "/my-route/hello" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
headers = {
|
|
version = { "v1" },
|
|
location = { "us-east" },
|
|
},
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/my-route/hello", "domain.org", "http",
|
|
nil, nil, nil, nil, nil, {
|
|
version = "v1",
|
|
location = "us-east",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route", match_t.matches.uri)
|
|
assert.same({ version = "v1", location = "us-east" },
|
|
match_t.matches.headers)
|
|
end
|
|
|
|
local match_t = router:select("GET", "/my-route/hello/world", "http",
|
|
"domain.org", nil, nil, nil, nil, nil, {
|
|
version = "v1",
|
|
location = "us-east",
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same("/my-route", match_t.matches.uri)
|
|
assert.same({ version = "v1", location = "us-east" },
|
|
match_t.matches.headers)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
describe("misses", function()
|
|
it("invalid [host]", function()
|
|
assert.is_nil(router:select("GET", "/", "domain-3.org"))
|
|
end)
|
|
|
|
it("invalid host in [host + uri]", function()
|
|
assert.is_nil(router:select("GET", "/route-4", "domain-3.org"))
|
|
end)
|
|
|
|
it("invalid host in [host + method]", function()
|
|
assert.is_nil(router:select("GET", "/", "domain-3.org"))
|
|
end)
|
|
|
|
it("invalid method in [host + uri + method]", function()
|
|
assert.is_nil(router:select("GET", "/some-uri", "domain-with-uri-2.org"))
|
|
end)
|
|
|
|
it("invalid uri in [host + uri + method]", function()
|
|
assert.is_nil(router:select("PUT", "/some-uri-foo",
|
|
"domain-with-uri-2.org"))
|
|
end)
|
|
|
|
it("does not match when given [uri] is in URI but not in prefix", function()
|
|
local match_t = router:select("GET", "/some-other-prefix/my-route",
|
|
"domain.org")
|
|
assert.is_nil(match_t)
|
|
end)
|
|
|
|
it("invalid [headers]", function()
|
|
assert.is_nil(router:select("GET", "/", nil, "http", nil, nil, nil, nil, nil,
|
|
{ location = "invalid-location" }))
|
|
end)
|
|
|
|
it("invalid headers in [headers + uri]", function()
|
|
assert.is_nil(router:select("GET", "/headers-uri",
|
|
nil, "http", nil, nil, nil, nil, nil,
|
|
{ location = "invalid-location" }))
|
|
end)
|
|
|
|
it("invalid headers in [headers + uri + method]", function()
|
|
assert.is_nil(router:select("PUT", "/headers-uri-method",
|
|
nil, "http", nil, nil, nil, nil, nil,
|
|
{ location = "invalid-location" }))
|
|
end)
|
|
|
|
it("invalid headers in [headers + host + uri + method]", function()
|
|
assert.is_nil(router:select("PUT", "/headers-host-uri-method",
|
|
nil, "http", nil, nil, nil, nil, nil,
|
|
{ location = "invalid-location",
|
|
host = "domain-with-headers-1.org" }))
|
|
end)
|
|
end)
|
|
|
|
describe("#benchmarks", function()
|
|
--[[
|
|
Run:
|
|
$ busted --tags=benchmarks <router_spec.lua>
|
|
|
|
To estimate how much time matching an route in a worst-case scenario
|
|
with a set of ~1000 registered routes would take.
|
|
|
|
We are aiming at sub-ms latency.
|
|
]]
|
|
|
|
describe("plain [host]", function()
|
|
local router
|
|
local target_domain
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
for i = 1, 10^5 do
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a1" .. string.format("%06d", i),
|
|
hosts = { "domain-" .. i .. ".org" },
|
|
},
|
|
}
|
|
end
|
|
|
|
target_domain = "domain-" .. #benchmark_use_cases .. ".org"
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("GET", "/", target_domain)
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("[method + uri + host]", function()
|
|
local router
|
|
local target_uri
|
|
local target_domain
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
local n = 10^5
|
|
|
|
for i = 1, n - 1 do
|
|
-- insert a lot of routes that don't match (missing methods)
|
|
-- but have conflicting paths and hosts (domain-<n>.org)
|
|
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a1" .. string.format("%06d", i),
|
|
hosts = { "domain-" .. n .. ".org" },
|
|
paths = { "/my-route-" .. n },
|
|
},
|
|
}
|
|
end
|
|
|
|
-- insert our target route, which has the proper method as well
|
|
benchmark_use_cases[n] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a1" .. string.format("%06d", n),
|
|
hosts = { "domain-" .. n .. ".org" },
|
|
methods = { "POST" },
|
|
paths = { "/my-route-" .. n },
|
|
},
|
|
}
|
|
|
|
target_uri = "/my-route-" .. n
|
|
target_domain = "domain-" .. n .. ".org"
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("POST", target_uri, target_domain)
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("[headers]", function()
|
|
describe("single key", function()
|
|
local router
|
|
local target_location
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
local n = 10^5
|
|
|
|
for i = 1, n do
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a1" .. string.format("%06d", i),
|
|
headers = {
|
|
location = { "somewhere-" .. i },
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
target_location = "somewhere-" .. n
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("GET", "/",
|
|
nil, "http", nil, nil, nil, nil, nil,
|
|
{ location = target_location })
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route,
|
|
match_t.route)
|
|
end)
|
|
end)
|
|
|
|
if flavor == "traditional" then
|
|
describe("10^4 keys", function()
|
|
local router
|
|
local target_val
|
|
local target_key
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
local n = 10^5
|
|
|
|
for i = 1, n do
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a1" .. string.format("%06d", i),
|
|
headers = {
|
|
["key-" .. i] = { "somewhere" },
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
target_key = "key-" .. n
|
|
target_val = "somewhere"
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("GET", "/",
|
|
nil, "http", nil, nil, nil, nil, nil,
|
|
{ [target_key] = target_val })
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route,
|
|
match_t.route)
|
|
end)
|
|
end)
|
|
end
|
|
end)
|
|
|
|
describe("multiple routes of same category with identical values", function()
|
|
local router
|
|
local target_uri
|
|
local target_domain
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
local n = 10^5
|
|
|
|
for i = 1, n - 1 do
|
|
-- all our routes here use domain.org as the domain
|
|
-- they all are [host + uri] category
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6" .. string.format("%06d", i),
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-route-" .. n },
|
|
},
|
|
}
|
|
end
|
|
|
|
-- this one too, but our target will be a
|
|
-- different URI
|
|
benchmark_use_cases[n] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6ffffff",
|
|
hosts = { "domain.org" },
|
|
paths = { "/my-real-route" },
|
|
},
|
|
}
|
|
|
|
target_uri = "/my-real-route"
|
|
target_domain = "domain.org"
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("GET", target_uri, target_domain)
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
describe("[method + uri + host + headers]", function()
|
|
local router
|
|
local target_uri
|
|
local target_domain
|
|
local target_location
|
|
local benchmark_use_cases = {}
|
|
|
|
lazy_setup(function()
|
|
local n = 10^5
|
|
|
|
for i = 1, n - 1 do
|
|
-- insert a lot of routes that don't match (missing methods)
|
|
-- but have conflicting paths and hosts (domain-<n>.org)
|
|
benchmark_use_cases[i] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6" .. string.format("%06d", i),
|
|
hosts = { "domain-" .. n .. ".org" },
|
|
paths = { "/my-route-" .. n },
|
|
headers = {
|
|
location = { "somewhere-" .. n },
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
-- insert our target route, which has the proper method as well
|
|
benchmark_use_cases[n] = {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6ffffff",
|
|
hosts = { "domain-" .. n .. ".org" },
|
|
headers = {
|
|
location = { "somewhere-" .. n },
|
|
},
|
|
methods = { "POST" },
|
|
paths = { "/my-route-" .. n },
|
|
},
|
|
}
|
|
|
|
target_uri = "/my-route-" .. n
|
|
target_domain = "domain-" .. n .. ".org"
|
|
target_location = "somewhere-" .. n
|
|
router = assert(new_router(benchmark_use_cases))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
-- this avoids memory leakage
|
|
router = nil
|
|
benchmark_use_cases = nil
|
|
end)
|
|
|
|
it("takes < 1ms", function()
|
|
local match_t = router:select("POST", target_uri, target_domain, "http",
|
|
nil, nil, nil, nil, nil, {
|
|
location = target_location,
|
|
})
|
|
assert.truthy(match_t)
|
|
assert.same(benchmark_use_cases[#benchmark_use_cases].route,
|
|
match_t.route)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("[errors]", function()
|
|
it("enforces args types", function()
|
|
assert.error_matches(function()
|
|
router:select(1)
|
|
end, "method must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", 1)
|
|
end, "uri must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", 1)
|
|
end, "host must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", 1)
|
|
end, "scheme must be a string", nil, true)
|
|
|
|
if flavor == "traditional" then
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", 1)
|
|
end, "src_ip must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", nil, "")
|
|
end, "src_port must be a number", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", nil, nil, 1)
|
|
end, "dst_ip must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", nil, nil, nil, "")
|
|
end, "dst_port must be a number", nil, true)
|
|
end
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", nil, nil, nil, nil, 1)
|
|
end, "sni must be a string", nil, true)
|
|
|
|
assert.error_matches(function()
|
|
router:select("GET", "/", "", "http", nil, nil, nil, nil, nil, 1)
|
|
end, "headers must be a table", nil, true)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("exec()", function()
|
|
it("returns parsed upstream_url + upstream_uri", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "https"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/my-route-2" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
local get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_headers).was_not_called()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
|
|
-- upstream_url_t
|
|
if flavor == "traditional" then
|
|
assert.equal("http", match_t.upstream_url_t.scheme)
|
|
end
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal(80, match_t.upstream_url_t.port)
|
|
|
|
-- upstream_uri
|
|
assert.is_nil(match_t.upstream_host) -- only when `preserve_host = true`
|
|
assert.equal("/my-route", match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", "/my-route-2", { host = "domain.org" })
|
|
get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.spy(get_headers).was_not_called()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
|
|
-- upstream_url_t
|
|
if flavor == "traditional" then
|
|
assert.equal("https", match_t.upstream_url_t.scheme)
|
|
end
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal(443, match_t.upstream_url_t.port)
|
|
|
|
-- upstream_uri
|
|
assert.is_nil(match_t.upstream_host) -- only when `preserve_host = true`
|
|
assert.equal("/my-route-2", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("returns matched_host + matched_uri + matched_method + matched_headers", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { "host.com" },
|
|
methods = { "GET" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = { "host.com" },
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = { "*.host.com" },
|
|
headers = {
|
|
location = { "my-location-1", "my-location-2" },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
paths = { [[~/users/\d+/profile]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "host.com" })
|
|
local get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_headers).was_called(1)
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.equal("host.com", match_t.matches.host)
|
|
assert.equal("/my-route", match_t.matches.uri)
|
|
assert.equal("GET", match_t.matches.method)
|
|
end
|
|
assert.is_nil(match_t.matches.headers)
|
|
|
|
_ngx = mock_ngx("GET", "/my-route/prefix/match", { host = "host.com" })
|
|
get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.spy(get_headers).was_called(1)
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.equal("host.com", match_t.matches.host)
|
|
assert.equal("/my-route", match_t.matches.uri)
|
|
assert.equal("GET", match_t.matches.method)
|
|
end
|
|
assert.is_nil(match_t.matches.headers)
|
|
|
|
_ngx = mock_ngx("POST", "/my-route", { host = "host.com" })
|
|
get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.spy(get_headers).was_called(1)
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.equal("host.com", match_t.matches.host)
|
|
assert.equal("/my-route", match_t.matches.uri)
|
|
end
|
|
assert.is_nil(match_t.matches.method)
|
|
assert.is_nil(match_t.matches.headers)
|
|
|
|
_ngx = mock_ngx("GET", "/", {
|
|
host = "test.host.com",
|
|
location = "my-location-1"
|
|
})
|
|
get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.spy(get_headers).was_called(1)
|
|
assert.same(use_case_routes[3].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.equal("*.host.com", match_t.matches.host)
|
|
assert.same({ location = "my-location-1" }, match_t.matches.headers)
|
|
end
|
|
assert.is_nil(match_t.matches.uri)
|
|
assert.is_nil(match_t.matches.method)
|
|
|
|
_ngx = mock_ngx("GET", "/users/123/profile", { host = "domain.org" })
|
|
get_headers = spy.on(_ngx.req, "get_headers")
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.spy(get_headers).was_called(1)
|
|
assert.same(use_case_routes[4].route, match_t.route)
|
|
assert.is_nil(match_t.matches.host)
|
|
if flavor == "traditional" then
|
|
assert.equal([[/users/\d+/profile]], match_t.matches.uri)
|
|
end
|
|
assert.is_nil(match_t.matches.method)
|
|
assert.is_nil(match_t.matches.headers)
|
|
end)
|
|
|
|
it("uri_captures works well with the optional capture group. Fix #13014", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/(users/)?1984/(?<subpath>profile)$]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/1984/profile", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.falsy(match_t.matches.uri_captures[1])
|
|
assert.equal("profile", match_t.matches.uri_captures.subpath)
|
|
assert.is_nil(match_t.matches.uri_captures.scope)
|
|
end)
|
|
|
|
it("returns uri_captures from a [uri regex]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/(?P<user_id>\d+)/profile/?(?P<scope>[a-z]*)]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/users/1984/profile",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal("1984", match_t.matches.uri_captures[1])
|
|
assert.equal("1984", match_t.matches.uri_captures.user_id)
|
|
assert.equal("", match_t.matches.uri_captures[2])
|
|
assert.equal("", match_t.matches.uri_captures.scope)
|
|
-- returns the full match as well
|
|
assert.equal("/users/1984/profile", match_t.matches.uri_captures[0])
|
|
-- no stripped_uri capture
|
|
assert.is_nil(match_t.matches.uri_captures.stripped_uri)
|
|
assert.equal(2, #match_t.matches.uri_captures)
|
|
|
|
-- again, this time from the LRU cache
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.equal("1984", match_t.matches.uri_captures[1])
|
|
assert.equal("1984", match_t.matches.uri_captures.user_id)
|
|
assert.equal("", match_t.matches.uri_captures[2])
|
|
assert.equal("", match_t.matches.uri_captures.scope)
|
|
-- returns the full match as well
|
|
assert.equal("/users/1984/profile", match_t.matches.uri_captures[0])
|
|
-- no stripped_uri capture
|
|
assert.is_nil(match_t.matches.uri_captures.stripped_uri)
|
|
assert.equal(2, #match_t.matches.uri_captures)
|
|
|
|
_ngx = mock_ngx("GET", "/users/1984/profile/email",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.equal("1984", match_t.matches.uri_captures[1])
|
|
assert.equal("1984", match_t.matches.uri_captures.user_id)
|
|
assert.equal("email", match_t.matches.uri_captures[2])
|
|
assert.equal("email", match_t.matches.uri_captures.scope)
|
|
-- returns the full match as well
|
|
assert.equal("/users/1984/profile/email", match_t.matches.uri_captures[0])
|
|
-- no stripped_uri capture
|
|
assert.is_nil(match_t.matches.uri_captures.stripped_uri)
|
|
assert.equal(2, #match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("returns uri_captures normalized, fix #7913", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/(?P<fullname>[a-zA-Z\s\d%]+)/profile/?(?P<scope>[a-z]*)]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/users/%6aohn%20doe/profile", { host = "domain.org" })
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal("john doe", match_t.matches.uri_captures[1])
|
|
assert.equal("john doe", match_t.matches.uri_captures.fullname)
|
|
assert.equal("", match_t.matches.uri_captures[2])
|
|
assert.equal("", match_t.matches.uri_captures.scope)
|
|
-- returns the full match as well
|
|
assert.equal("/users/john doe/profile", match_t.matches.uri_captures[0])
|
|
-- no stripped_uri capture
|
|
assert.is_nil(match_t.matches.uri_captures.stripped_uri)
|
|
assert.equal(2, #match_t.matches.uri_captures)
|
|
|
|
-- again, this time from the LRU cache
|
|
local match_t = router:exec()
|
|
assert.equal("john doe", match_t.matches.uri_captures[1])
|
|
assert.equal("john doe", match_t.matches.uri_captures.fullname)
|
|
assert.equal("", match_t.matches.uri_captures[2])
|
|
assert.equal("", match_t.matches.uri_captures.scope)
|
|
-- returns the full match as well
|
|
assert.equal("/users/john doe/profile", match_t.matches.uri_captures[0])
|
|
-- no stripped_uri capture
|
|
assert.is_nil(match_t.matches.uri_captures.stripped_uri)
|
|
assert.equal(2, #match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("returns no uri_captures from a [uri prefix] match", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/hello" },
|
|
strip_path = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/hello/world", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal("/world", match_t.upstream_uri)
|
|
assert.is_nil(match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("returns no uri_captures from a [uri regex] match without groups", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/\d+/profile]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/users/1984/profile",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.is_nil(match_t.matches.uri_captures)
|
|
end)
|
|
|
|
it("parses path component from upstream_url property", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
path = "/get",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
|
|
if flavor == "traditional" then
|
|
assert.equal("/get", match_t.upstream_url_t.path)
|
|
end
|
|
end)
|
|
|
|
it("parses upstream_url port", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
port = 8080,
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/my-route" },
|
|
},
|
|
},
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
port = 8443,
|
|
protocol = "https"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { "/my-route-2" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal(8080, match_t.upstream_url_t.port)
|
|
|
|
_ngx = mock_ngx("GET", "/my-route-2", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.equal(8443, match_t.upstream_url_t.port)
|
|
end)
|
|
|
|
it("allows url encoded paths if they are reserved characters", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/endel%2Fst" },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/endel%2Fst", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/endel%2Fst", match_t.upstream_uri)
|
|
end)
|
|
|
|
describe("stripped paths #strip", function()
|
|
local router
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = uuid(),
|
|
paths = { "/my-route", "/xx-route" }, -- need to have same length for get_priority to work
|
|
strip_path = true
|
|
}
|
|
},
|
|
-- don't strip this route's matching URI
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = uuid(),
|
|
methods = { "POST" },
|
|
paths = { "/my-route", "/xx-route" }, -- need to have same length for get_priority to work
|
|
strip_path = false,
|
|
},
|
|
},
|
|
}
|
|
|
|
lazy_setup(function()
|
|
-- deep copy use_case_routes in case it changes
|
|
router = assert(new_router(deep_copy(use_case_routes)))
|
|
end)
|
|
|
|
it("strips the specified paths from the given uri if matching", function()
|
|
local _ngx = mock_ngx("GET", "/my-route/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/hello/world", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("strips if matched URI is plain (not a prefix)", function()
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("doesn't strip if 'strip_uri' is not enabled", function()
|
|
local _ngx = mock_ngx("POST", "/my-route/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.is_nil(match_t.prefix)
|
|
assert.equal("/my-route/hello/world", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("normalized client URI before matching and proxying", function()
|
|
local _ngx = mock_ngx("POST", "/my-route/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.is_nil(match_t.prefix)
|
|
assert.equal("/my-route/hello/world", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("does not strips root / URI", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/" },
|
|
strip_path = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
-- deep copy use_case_routes in case it changes
|
|
local router = assert(new_router(deep_copy(use_case_routes)))
|
|
|
|
local _ngx = mock_ngx("POST", "/my-route/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/", match_t.prefix)
|
|
assert.equal("/my-route/hello/world", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("can find an route with stripped URI several times in a row", function()
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("can proxy an route with stripped URI with different URIs in a row", function()
|
|
local _ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", "/xx-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/xx-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", "/my-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/my-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", "/xx-route", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/xx-route", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("strips url encoded paths", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/endel%2Fst" },
|
|
strip_path = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/endel%2Fst", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("/endel%2Fst", match_t.prefix)
|
|
assert.equal("/", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("strips a [uri regex]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/\d+/profile]] },
|
|
strip_path = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/users/123/profile/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal("/users/123/profile", match_t.prefix)
|
|
assert.equal("/hello/world", match_t.upstream_uri)
|
|
end)
|
|
|
|
it("strips a [uri regex] with a capture group", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[~/users/(\d+)/profile]] },
|
|
strip_path = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
local _ngx = mock_ngx("GET", "/users/123/profile/hello/world",
|
|
{ host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.equal("/users/123/profile", match_t.prefix)
|
|
assert.equal("/hello/world", match_t.upstream_uri)
|
|
end)
|
|
end)
|
|
|
|
describe("preserve Host header", function()
|
|
local router
|
|
local use_case_routes = {
|
|
-- use the request's Host header
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
preserve_host = true,
|
|
hosts = { "preserve.com" },
|
|
},
|
|
},
|
|
-- use the route's upstream_url's Host
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
preserve_host = false,
|
|
hosts = { "discard.com" },
|
|
},
|
|
},
|
|
}
|
|
|
|
lazy_setup(function()
|
|
router = assert(new_router(use_case_routes))
|
|
end)
|
|
|
|
describe("when preserve_host is true", function()
|
|
local host = "preserve.com"
|
|
|
|
it("uses the request's Host header", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal(host, match_t.upstream_host)
|
|
end)
|
|
|
|
it("uses the request's Host header incl. port", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host .. ":123" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal(host .. ":123", match_t.upstream_host)
|
|
end)
|
|
|
|
it("does not change the target upstream", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
end)
|
|
|
|
it("uses the request's Host header when `grab_header` is disabled", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
name = "route-1",
|
|
preserve_host = true,
|
|
paths = { "/foo" },
|
|
},
|
|
upstream_url = "http://example.org",
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/foo", { host = "preserve.com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("preserve.com", match_t.upstream_host)
|
|
end)
|
|
|
|
it("uses the request's Host header if an route with no host was cached", function()
|
|
-- This is a regression test for:
|
|
-- https://github.com/Kong/kong/issues/2825
|
|
-- Ensure cached routes (in the LRU cache) still get proxied with the
|
|
-- correct Host header when preserve_host = true and no registered
|
|
-- route has a `hosts` property.
|
|
|
|
local use_case_routes = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
name = "no-host",
|
|
paths = { "/nohost" },
|
|
preserve_host = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/nohost", { host = "domain1.com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("domain1.com", match_t.upstream_host)
|
|
|
|
_ngx = mock_ngx("GET", "/nohost", { host = "domain2.com" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("domain2.com", match_t.upstream_host)
|
|
end)
|
|
end)
|
|
|
|
describe("when preserve_host is false", function()
|
|
local host = "discard.com"
|
|
|
|
it("does not change the target upstream", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
end)
|
|
|
|
it("does not set the host_header", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.is_nil(match_t.upstream_host)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("preserve Host header #grpc", function()
|
|
local router
|
|
local use_case_routes = {
|
|
-- use the request's Host header
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "grpc"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
preserve_host = true,
|
|
hosts = { "preserve.com" },
|
|
},
|
|
},
|
|
-- use the route's upstream_url's Host
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "grpc"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
preserve_host = false,
|
|
hosts = { "discard.com" },
|
|
},
|
|
},
|
|
}
|
|
|
|
lazy_setup(function()
|
|
router = assert(new_router(use_case_routes))
|
|
end)
|
|
|
|
describe("when preserve_host is true", function()
|
|
local host = "preserve.com"
|
|
|
|
it("uses the request's Host header", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal(host, match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
|
|
it("uses the request's Host header incl. port", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host .. ":123" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal(host .. ":123", match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
|
|
it("does not change the target upstream", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
|
|
it("uses the request's Host header when `grab_header` is disabled", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
protocol = "grpc",
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
name = "route-1",
|
|
preserve_host = true,
|
|
paths = { "/foo" },
|
|
},
|
|
upstream_url = "http://example.org",
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/foo", { host = "preserve.com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("preserve.com", match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
|
|
it("uses the request's Host header if an route with no host was cached", function()
|
|
-- This is a regression test for:
|
|
-- https://github.com/Kong/kong/issues/2825
|
|
-- Ensure cached routes (in the LRU cache) still get proxied with the
|
|
-- correct Host header when preserve_host = true and no registered
|
|
-- route has a `hosts` property.
|
|
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
protocol = "grpc",
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
name = "no-host",
|
|
paths = { "/nohost" },
|
|
preserve_host = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "/nohost", { host = "domain1.com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("domain1.com", match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
|
|
_ngx = mock_ngx("GET", "/nohost", { host = "domain2.com" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
assert.equal("domain2.com", match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
end)
|
|
|
|
describe("when preserve_host is false", function()
|
|
local host = "discard.com"
|
|
|
|
it("does not change the target upstream", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
|
|
it("does not set the host_header", function()
|
|
local _ngx = mock_ngx("GET", "/", { host = host })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
assert.is_nil(match_t.upstream_host)
|
|
assert.equal("grpc", match_t.service.protocol)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("#slash handling", function()
|
|
for i, line in ipairs(path_handling_tests) do
|
|
for j, test in ipairs(line:expand()) do
|
|
if flavor == "traditional" or test.path_handling == "v0" then
|
|
local strip = test.strip_path and "on" or "off"
|
|
local route_uri_or_host
|
|
if test.route_path then
|
|
route_uri_or_host = "uri " .. test.route_path
|
|
else
|
|
route_uri_or_host = "host localbin-" .. i .. "-" .. j .. ".com"
|
|
end
|
|
|
|
local description = string.format("(%d-%d) plain, %s with %s, strip = %s, %s. req: %s",
|
|
i, j, test.service_path, route_uri_or_host, strip, test.path_handling, test.request_path)
|
|
|
|
it(description, function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
protocol = "http",
|
|
name = "service-invalid",
|
|
path = test.service_path,
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6" .. string.format("%03d%03d", i, j),
|
|
strip_path = test.strip_path,
|
|
path_handling = test.path_handling,
|
|
-- only add the header is no path is provided
|
|
hosts = test.service_path == nil and nil or { "localbin-" .. i .. "-" .. j .. ".com" },
|
|
paths = { test.route_path },
|
|
},
|
|
}
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes) )
|
|
local _ngx = mock_ngx("GET", test.request_path, { host = "localbin-" .. i .. "-" .. j .. ".com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(test.service_path, match_t.upstream_url_t.path)
|
|
end
|
|
assert.same(test.expected_path, match_t.upstream_uri)
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- this is identical to the tests above, except that for the path we match
|
|
-- with an injected regex sequence, effectively transforming the path
|
|
-- match into a regex match
|
|
for i, line in ipairs(path_handling_tests) do
|
|
if line.route_path then -- skip test cases which match on host
|
|
for j, test in ipairs(line:expand()) do
|
|
if flavor == "traditional" or test.path_handling == "v0" then
|
|
local strip = test.strip_path and "on" or "off"
|
|
local regex = "~/[0]?" .. test.route_path:sub(2, -1)
|
|
local description = string.format("(%d-%d) regex, %s with %s, strip = %s, %s. req: %s",
|
|
i, j, test.service_path, regex, strip, test.path_handling, test.request_path)
|
|
|
|
it(description, function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
protocol = "http",
|
|
name = "service-invalid",
|
|
path = test.service_path,
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6" .. string.format("%03d%03d", i, j),
|
|
strip_path = test.strip_path,
|
|
-- only add the header is no path is provided
|
|
path_handling = test.path_handling,
|
|
hosts = { "localbin-" .. i .. ".com" },
|
|
paths = { regex },
|
|
},
|
|
}
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes) )
|
|
local _ngx = mock_ngx("GET", test.request_path, { host = "localbin-" .. i .. ".com" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
if flavor == "traditional" then
|
|
assert.same(test.service_path, match_t.upstream_url_t.path)
|
|
end
|
|
assert.same(test.expected_path, match_t.upstream_uri)
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
it("works with special characters('\"','\\')", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[/\d]] },
|
|
},
|
|
},
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "https"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = { [[~/\d+"]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", [[/\d]], { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.same(use_case_routes[1].route, match_t.route)
|
|
|
|
-- upstream_url_t
|
|
if flavor == "traditional" then
|
|
assert.equal("http", match_t.upstream_url_t.scheme)
|
|
end
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal(80, match_t.upstream_url_t.port)
|
|
|
|
-- upstream_uri
|
|
assert.is_nil(match_t.upstream_host) -- only when `preserve_host = true`
|
|
assert.equal([[/\d]], match_t.upstream_uri)
|
|
|
|
_ngx = mock_ngx("GET", [[/123"]], { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
match_t = router:exec()
|
|
assert.same(use_case_routes[2].route, match_t.route)
|
|
|
|
-- upstream_url_t
|
|
if flavor == "traditional" then
|
|
assert.equal("https", match_t.upstream_url_t.scheme)
|
|
end
|
|
assert.equal("example.org", match_t.upstream_url_t.host)
|
|
assert.equal(443, match_t.upstream_url_t.port)
|
|
|
|
-- upstream_uri
|
|
assert.is_nil(match_t.upstream_host) -- only when `preserve_host = true`
|
|
assert.equal([[/123"]], match_t.upstream_uri)
|
|
end)
|
|
|
|
if flavor == "traditional_compatible" or flavor == "expressions" then
|
|
it("gracefully handles invalid utf-8 sequences", function()
|
|
local use_case_routes = {
|
|
{
|
|
service = {
|
|
name = "service-invalid",
|
|
host = "example.org",
|
|
protocol = "http"
|
|
},
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { [[/hello]] },
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case_routes))
|
|
local _ngx = mock_ngx("GET", "\xfc\x80\x80\x80\x80\xaf", { host = "example.org" })
|
|
local log_spy = spy.on(_ngx, "log")
|
|
|
|
router._set_ngx(_ngx)
|
|
|
|
local match_t = router:exec()
|
|
assert.is_nil(match_t)
|
|
|
|
assert.spy(log_spy).was.called_with(ngx.ERR, "router returned an error: ",
|
|
"invalid utf-8 sequence of 1 bytes from index 0",
|
|
", 404 Not Found will be returned for the current request")
|
|
end)
|
|
end
|
|
end)
|
|
|
|
|
|
-- flavor == "traditional"/"traditional_compatible"/"expressions"
|
|
describe("#stream context", function()
|
|
-- enable stream subsystem
|
|
reload_router(flavor, "stream")
|
|
|
|
describe("[sources]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- plain
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tcp", },
|
|
sources = {
|
|
{ ip = "127.0.0.1" },
|
|
{ ip = "127.0.0.2" },
|
|
}
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tcp", },
|
|
sources = {
|
|
{ port = 65001 },
|
|
{ port = 65002 },
|
|
}
|
|
}
|
|
},
|
|
-- range
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
protocols = { "tcp", },
|
|
sources = {
|
|
{ ip = "127.168.0.0/8" },
|
|
}
|
|
}
|
|
},
|
|
-- ip + port
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
protocols = { "tcp", },
|
|
sources = {
|
|
{ ip = "127.0.0.1", port = 65001 },
|
|
}
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8105",
|
|
protocols = { "tcp", },
|
|
sources = {
|
|
{ ip = "127.0.0.2", port = 65300 },
|
|
{ ip = "127.168.0.0/16", port = 65301 },
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
|
|
-- let atc-router happy
|
|
local _ngx = mock_ngx("GET", "/", { a = "1" })
|
|
router._set_ngx(_ngx)
|
|
end)
|
|
|
|
it("[src_ip]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.0.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select(nil, nil, nil, "tcp", "127.0.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("[src_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.0.0.3", 65001)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("[src_ip] range match", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.168.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
it("[src_ip] + [src_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.0.0.1", 65001)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
end)
|
|
|
|
it("[src_ip] range match + [src_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.168.10.1", 65301)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[5].route, match_t.route)
|
|
end)
|
|
|
|
it("[src_ip] no match", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", "10.0.0.1")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select(nil, nil, nil, "tcp", "10.0.0.2", 65301)
|
|
assert.falsy(match_t)
|
|
end)
|
|
end)
|
|
|
|
|
|
describe("[destinations]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- plain
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tcp", },
|
|
destinations = {
|
|
{ ip = "127.0.0.1" },
|
|
{ ip = "127.0.0.2" },
|
|
}
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tcp", },
|
|
destinations = {
|
|
{ port = 65001 },
|
|
{ port = 65002 },
|
|
}
|
|
}
|
|
},
|
|
-- range
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
protocols = { "tcp", },
|
|
destinations = {
|
|
{ ip = "127.168.0.0/8" },
|
|
}
|
|
}
|
|
},
|
|
-- ip + port
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
protocols = { "tcp", },
|
|
destinations = {
|
|
{ ip = "127.0.0.1", port = 65001 },
|
|
}
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8105",
|
|
protocols = { "tcp", },
|
|
destinations = {
|
|
{ ip = "127.0.0.2", port = 65300 },
|
|
{ ip = "127.168.0.0/16", port = 65301 },
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("[dst_ip]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.0.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.0.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("[dst_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.0.0.3", 65001)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("[dst_ip] range match", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.168.0.1")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
it("[dst_ip] + [dst_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.0.0.1", 65001)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[4].route, match_t.route)
|
|
end)
|
|
|
|
it("[dst_ip] range match + [dst_port]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"127.168.10.1", 65301)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[5].route, match_t.route)
|
|
end)
|
|
|
|
it("[dst_ip] no match", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"10.0.0.1")
|
|
assert.falsy(match_t)
|
|
|
|
match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"10.0.0.2", 65301)
|
|
assert.falsy(match_t)
|
|
end)
|
|
end)
|
|
|
|
|
|
describe("[snis]", function()
|
|
local use_case, use_case_ignore_sni, router, router_ignore_sni
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
snis = { "www.example.org" }
|
|
}
|
|
},
|
|
-- see #6425
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
hosts = {
|
|
"sni.example.com",
|
|
},
|
|
protocols = {
|
|
"http", "https",
|
|
},
|
|
snis = {
|
|
"sni.example.com",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
hosts = {
|
|
"sni.example.com",
|
|
},
|
|
protocols = {
|
|
"http",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
use_case_ignore_sni = {
|
|
-- see #6425
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8104",
|
|
hosts = {
|
|
"sni.example.com",
|
|
},
|
|
protocols = {
|
|
"http", "https",
|
|
},
|
|
snis = {
|
|
"sni.example.com",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
router_ignore_sni = assert(new_router(use_case_ignore_sni))
|
|
end)
|
|
|
|
it_trad_only("[sni]", function()
|
|
local match_t = router:select(nil, nil, nil, "tcp", nil, nil, nil, nil,
|
|
"www.example.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it_trad_only("[sni] is ignored for http request without shadowing routes with `protocols={'http'}`. Fixes #6425", function()
|
|
local match_t = router_ignore_sni:select(nil, nil, "sni.example.com",
|
|
"http", nil, nil, nil, nil,
|
|
nil)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case_ignore_sni[1].route, match_t.route)
|
|
|
|
match_t = router:select(nil, nil, "sni.example.com",
|
|
"http", nil, nil, nil, nil,
|
|
nil)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
end)
|
|
|
|
|
|
it("[sni] has higher priority than [src] or [dst]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tls", },
|
|
snis = { "www.example.org" },
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tls", },
|
|
sources = {
|
|
{ ip = "127.0.0.1" },
|
|
}
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
protocols = { "tls", },
|
|
destinations = {
|
|
{ ip = "172.168.0.1" },
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.0.0.1", nil,
|
|
nil, nil, "www.example.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
match_t = router:select(nil, nil, nil, "tcp", nil, nil,
|
|
"172.168.0.1", nil, "www.example.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("[src] + [dst] has higher priority than [sni]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tls", },
|
|
snis = { "www.example.org" },
|
|
}
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tls", },
|
|
sources = {
|
|
{ ip = "127.0.0.1" },
|
|
},
|
|
destinations = {
|
|
{ ip = "172.168.0.1" },
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select(nil, nil, nil, "tcp", "127.0.0.1", nil,
|
|
"172.168.0.1", nil, "www.example.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
end)
|
|
-- flavor == "traditional"/"traditional_compatible"/"expressions"
|
|
|
|
-- only traditional_compatible should check 'empty' fields
|
|
if flavor == "traditional_compatible" then
|
|
describe("#stream context", function()
|
|
-- enable stream subsystem
|
|
reload_router(flavor, "stream")
|
|
|
|
describe("check empty route fields", function()
|
|
local use_case
|
|
|
|
before_each(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tls" },
|
|
snis = { "www.example.org" },
|
|
sources = {
|
|
{ ip = "127.0.0.1" },
|
|
}
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
local empty_values = { {}, ngx.null, nil }
|
|
for i = 1, 3 do
|
|
local v = empty_values[i]
|
|
|
|
it("empty snis", function()
|
|
use_case[1].route.snis = v
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(net.src.ip == 127.0.0.1)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty snis and src cidr", function()
|
|
use_case[1].route.snis = v
|
|
use_case[1].route.sources = {{ ip = "127.168.0.1/8" },}
|
|
|
|
assert.equal(get_expression(use_case[1].route), [[(net.src.ip in 127.0.0.0/8)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty snis and dst cidr", function()
|
|
use_case[1].route.snis = v
|
|
use_case[1].route.destinations = {{ ip = "192.168.0.1/16" },}
|
|
|
|
assert.equal(get_expression(use_case[1].route),
|
|
[[(net.src.ip == 127.0.0.1) && (net.dst.ip in 192.168.0.0/16)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty sources", function()
|
|
use_case[1].route.sources = v
|
|
use_case[1].route.destinations = {{ ip = "192.168.0.1/16" },}
|
|
|
|
assert.equal(get_expression(use_case[1].route),
|
|
[[(net.protocol != r#"tls"# || (tls.sni == r#"www.example.org"#)) && (net.dst.ip in 192.168.0.0/16)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
|
|
it("empty destinations", function()
|
|
use_case[1].route.destinations = v
|
|
|
|
assert.equal(get_expression(use_case[1].route),
|
|
[[(net.protocol != r#"tls"# || (tls.sni == r#"www.example.org"#)) && (net.src.ip == 127.0.0.1)]])
|
|
assert(new_router(use_case))
|
|
end)
|
|
end
|
|
end)
|
|
end) -- #stream context
|
|
end -- if flavor == "traditional_compatible"
|
|
end) -- describe("Router (flavor =
|
|
end -- for _, flavor
|
|
|
|
|
|
for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
|
|
describe("[both regex and prefix with regex_priority]", function()
|
|
reload_router(flavor)
|
|
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- regex
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/.*"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
},
|
|
},
|
|
},
|
|
-- prefix
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/"
|
|
},
|
|
hosts = {
|
|
"domain-2.org",
|
|
},
|
|
regex_priority = 5
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
paths = {
|
|
"/v1"
|
|
},
|
|
hosts = {
|
|
"domain-2.org",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("[prefix matching ignore regex_priority]", function()
|
|
local match_t = router:select("GET", "/v1", "domain-2.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
end)
|
|
end -- for flavor
|
|
|
|
|
|
for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
|
|
describe("Router (flavor = " .. flavor .. ")", function()
|
|
reload_router(flavor)
|
|
|
|
describe("[both regex and prefix]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- regex + prefix
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"~/some/thing/else",
|
|
"/foo",
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
},
|
|
},
|
|
},
|
|
-- prefix
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/foo/bar"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
-- deep copy use_case in case it changes
|
|
router = assert(new_router(deep_copy(use_case)))
|
|
end)
|
|
|
|
it("[assigns different priorities to regex and non-regex path]", function()
|
|
local match_t = router:select("GET", "/some/thing/else", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
local match_t = router:select("GET", "/foo/bar", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
end)
|
|
|
|
describe("[overlapping prefixes]", function()
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- regex + prefix
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/foo",
|
|
"/foo/bar/baz"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
},
|
|
},
|
|
},
|
|
-- prefix
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
paths = {
|
|
"/foo/bar"
|
|
},
|
|
hosts = {
|
|
"domain-1.org",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
-- deep copy use_case in case it changes
|
|
router = assert(new_router(deep_copy(use_case)))
|
|
end)
|
|
|
|
it("[assigns different priorities to each path]", function()
|
|
local match_t = router:select("GET", "/foo", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
local match_t = router:select("GET", "/foo/bar", "domain-1.org")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
end)
|
|
|
|
it("[can create route with multiple paths and no service]", function()
|
|
local use_case = {
|
|
-- regex + prefix
|
|
{
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/foo",
|
|
"/foo/bar/baz"
|
|
},
|
|
},
|
|
}}
|
|
assert(new_router(use_case))
|
|
end)
|
|
end)
|
|
end
|
|
|
|
for _, flavor in ipairs({ "traditional_compatible", "expressions" }) do
|
|
|
|
describe("Router (flavor = " .. flavor .. ")", function()
|
|
reload_router(flavor)
|
|
|
|
it("[cache hit should be case sensitive]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = {
|
|
"/foo",
|
|
},
|
|
headers = {
|
|
test1 = { "Quote" },
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo", { test1 = "QUOTE", })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- first match, case insensitive
|
|
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
|
|
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", { test1 = "QuoTe", })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- case insensitive match
|
|
local match_t = router:exec(ctx)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
-- cache miss, case sensitive
|
|
assert.falsy(ctx.route_match_cached)
|
|
end)
|
|
|
|
it("[cache hit should have correct match_t.upstream_uri]", function()
|
|
local host = "example.com"
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
hosts = { host },
|
|
preserve_host = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo", { host = host, })
|
|
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)
|
|
assert.equal(host, match_t.upstream_host)
|
|
assert.same("/foo", match_t.upstream_uri)
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/bar", { host = host, })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- cache hit
|
|
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")
|
|
assert.equal(host, match_t.upstream_host)
|
|
assert.same("/bar", match_t.upstream_uri)
|
|
end)
|
|
end)
|
|
|
|
describe("Router (flavor = " .. flavor .. ")", function()
|
|
reload_router(flavor, "stream")
|
|
|
|
it("[#stream SNI-based routing does work using tls_passthrough]", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tls_passthrough", },
|
|
snis = { "www.example.com" },
|
|
preserve_host = true,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tls_passthrough", },
|
|
snis = { "www.example.org" },
|
|
preserve_host = true,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local _ngx = mock_ngx(nil, nil, nil, nil, {
|
|
ssl_preread_server_name = "www.example.com",
|
|
})
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local _ngx = mock_ngx(nil, nil, nil, nil, {
|
|
ssl_preread_server_name = "www.example.org",
|
|
})
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
end)
|
|
end -- for flavor
|
|
|
|
|
|
do
|
|
local flavor = "expressions"
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [stream]", function()
|
|
reload_router(flavor, "stream")
|
|
|
|
local use_case, router
|
|
|
|
local service = {
|
|
name = "service-invalid",
|
|
protocol = "tcp",
|
|
}
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
protocols = { "tls" },
|
|
expression = [[tls.sni == "www.example.com"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
protocols = { "tls_passthrough" },
|
|
expression = [[tls.sni == "www.example.org"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("exec() should match tls with tls.sni", function()
|
|
local _ngx = mock_ngx(nil, nil, nil, nil, {
|
|
remote_port = 1000,
|
|
server_port = 1000,
|
|
ssl_preread_server_name = "www.example.com",
|
|
})
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.truthy(match_t)
|
|
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("exec() should match tls_passthrough with tls.sni", function()
|
|
local _ngx = mock_ngx(nil, nil, nil, nil, {
|
|
remote_port = 1000,
|
|
server_port = 1000,
|
|
ssl_preread_server_name = "www.example.org",
|
|
})
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.truthy(match_t)
|
|
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
end)
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [http]", function()
|
|
reload_router(flavor)
|
|
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
-- query has one value
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.path == "/foo/bar" && http.queries.a == "1"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
-- query has no value or is empty string
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
expression = [[http.path == "/foo/bar" && http.queries.a == ""]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
-- query has multiple values
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
expression = [[http.path == "/foo/bar" && any(http.queries.a) == "2"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
router = assert(new_router(use_case))
|
|
end)
|
|
|
|
it("select() should match http.queries", function()
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, nil, {a = "1",})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, nil, {a = ""})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, nil, {a = true})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, nil, {a = {"2", "10"}})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, nil, {a = "x"})
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
it("exec() should match http.queries", function()
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { host = "domain.org"}, { a = "1"})
|
|
local get_uri_args = spy.on(_ngx.req, "get_uri_args")
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_uri_args).was_called(1)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { host = "domain.org"}, { a = ""})
|
|
local get_uri_args = spy.on(_ngx.req, "get_uri_args")
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_uri_args).was_called(1)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { host = "domain.org"}, { a = true})
|
|
local get_uri_args = spy.on(_ngx.req, "get_uri_args")
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_uri_args).was_called(1)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { host = "domain.org"}, { a = {"1", "2"}})
|
|
local get_uri_args = spy.on(_ngx.req, "get_uri_args")
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_uri_args).was_called(1)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { host = "domain.org"}, { a = "x"})
|
|
local get_uri_args = spy.on(_ngx.req, "get_uri_args")
|
|
|
|
router._set_ngx(_ngx)
|
|
local match_t = router:exec()
|
|
assert.spy(get_uri_args).was_called(1)
|
|
assert.falsy(match_t)
|
|
end)
|
|
|
|
end)
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [http]", function()
|
|
reload_router(flavor)
|
|
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.path == "/foo/bar" && http.headers.test1 == "Quote"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
expression = [[http.path == "/foo/bar" && lower(http.headers.test2) == "quote"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
it("select() should match with case sensitivity", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, {test1 = "quote"})
|
|
assert.falsy(match_t)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, {test1 = "quoTe"})
|
|
assert.falsy(match_t)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, {test1 = "Quote"})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("select() should match with lower() (case insensitive)", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, {test2 = "QuoTe"})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar", nil, nil, nil, nil, nil, nil, nil, {test2 = "QUOTE"})
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("exec() should hit cache with case sensitive", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo/bar", { test1 = "Quote", })
|
|
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/bar", { test1 = "QUOTE", })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- case sensitive not match
|
|
local match_t = router:exec(ctx)
|
|
assert.falsy(match_t)
|
|
assert.falsy(ctx.route_match_cached)
|
|
|
|
-- cache hit neg
|
|
local match_t = router:exec(ctx)
|
|
assert.falsy(match_t)
|
|
assert.same(ctx.route_match_cached, "neg")
|
|
end)
|
|
end)
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [http]", function()
|
|
reload_router(flavor)
|
|
|
|
local use_case, router
|
|
|
|
lazy_setup(function()
|
|
use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.host == "www.example.com" && net.port == 8000]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
expression = [[net.src.ip == 1.1.1.1 && net.dst.ip == 2.2.2.2 && http.method == "GET"]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
|
|
it("select() should convert 'net.port' to 'net.dst.port' and work well", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
-- let atc-router happy
|
|
local _ngx = mock_ngx("GET", "/", { a = "1" })
|
|
router._set_ngx(_ngx)
|
|
|
|
local match_t = router:select("GET", "/", "www.example.com:80")
|
|
assert.falsy(match_t)
|
|
|
|
local match_t = router:select("GET", "/", "www.example.com:8000")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
end)
|
|
|
|
it("exec() should use var.server_port if host has no port", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo", { host = "www.example.com" })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- no port provided
|
|
local match_t = router:exec()
|
|
assert.falsy(match_t)
|
|
|
|
-- var.server_port
|
|
_ngx.var.server_port = 8000
|
|
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
|
|
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")
|
|
end)
|
|
|
|
it("exec() should support net.src.* and net.dst.*", function()
|
|
router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo", { host = "domain.org" })
|
|
router._set_ngx(_ngx)
|
|
|
|
-- no ip address provided
|
|
local match_t = router:exec()
|
|
assert.falsy(match_t)
|
|
|
|
-- ip address
|
|
_ngx.var.remote_addr = "1.1.1.1"
|
|
_ngx.var.server_addr = "2.2.2.2"
|
|
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
|
|
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)
|
|
|
|
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 http.segments.* with len", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.path.segments.0 == "foo" && http.path.segments.len == 1]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].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,
|
|
},
|
|
},
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8103",
|
|
expression = [[http.path.segments.1_2 == r#"xxx/yyy"# && http.path.segments.len == 3]],
|
|
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)
|
|
|
|
local match_t = router:select("GET", "/foo/xxx/yyy")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/xxx/yyy/")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[3].route, match_t.route)
|
|
end)
|
|
|
|
it("select() accepts but does not match wrong http.segments.*", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.path.segments.4_1 == 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 normalize uri 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" && http.path.segments.2 == "baz" && ]] ..
|
|
[[http.path.segments.len == 3]],
|
|
priority = 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo/bar/baz", { a = "1", })
|
|
router._set_ngx(_ngx)
|
|
|
|
local match_t = router:exec(ctx)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local ctx = {}
|
|
local _ngx = mock_ngx("GET", "/foo//bar///baz//", { a = "1", })
|
|
router._set_ngx(_ngx)
|
|
|
|
local match_t = router:exec(ctx)
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
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)
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [http]", function()
|
|
reload_router(flavor)
|
|
|
|
it("select() should match not expression", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[!(http.path ^= r#"/foo"#)]],
|
|
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", "/xyz/hello-world/bar")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[1].route, match_t.route)
|
|
|
|
local match_t = router:select("GET", "/foo/bar")
|
|
assert.falsy(match_t)
|
|
end)
|
|
end)
|
|
|
|
describe("Router (flavor = " .. flavor .. ") [http]", function()
|
|
reload_router(flavor)
|
|
|
|
it("rejects other fields if expression field exists", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo" }, -- rejected
|
|
expression = [[http.path ^= r#"/bar"#]], -- effective
|
|
},
|
|
},
|
|
}
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo")
|
|
assert.falsy(match_t)
|
|
|
|
local match_t = router:select("GET", "/bar")
|
|
assert.truthy(match_t)
|
|
end)
|
|
|
|
it("expression route has higher priority than traditional route", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
paths = { "/foo" },
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
expression = [[http.path ^= r#"/foo"#]],
|
|
},
|
|
})
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo/bar")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
|
|
it("works when route.priority is near 2^46", function()
|
|
local use_case = {
|
|
{
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8101",
|
|
expression = [[http.path ^= r#"/foo"#]],
|
|
priority = 2^46 - 3,
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
|
|
table.insert(use_case, {
|
|
service = service,
|
|
route = {
|
|
id = "e8fb37f1-102d-461e-9c51-6608a6bb8102",
|
|
expression = [[http.path ^= r#"/foo"#]],
|
|
priority = 2^46 - 2,
|
|
},
|
|
})
|
|
|
|
local router = assert(new_router(use_case))
|
|
|
|
local match_t = router:select("GET", "/foo/bar")
|
|
assert.truthy(match_t)
|
|
assert.same(use_case[2].route, match_t.route)
|
|
end)
|
|
end)
|
|
end -- local flavor = "expressions"
|