diff --git a/kong/router/transform.lua b/kong/router/transform.lua index 351eb480e68b..ac4c2fb8382e 100644 --- a/kong/router/transform.lua +++ b/kong/router/transform.lua @@ -1,7 +1,7 @@ local bit = require("bit") local buffer = require("string.buffer") -local tb_new = require("table.new") local tb_nkeys = require("table.nkeys") +local tb_clear = require("table.clear") local uuid = require("resty.jit-uuid") local lrucache = require("resty.lrucache") local ipmatcher = require("resty.ipmatcher") @@ -12,7 +12,6 @@ local type = type local assert = assert local pairs = pairs local ipairs = ipairs -local tostring = tostring local tb_insert = table.insert local fmt = string.format local byte = string.byte @@ -670,68 +669,72 @@ end local uuid_generator = assert(uuid.factory_v5('7f145bf9-0dce-4f91-98eb-debbce4b9f6b')) -local function sort_by_regex_or_length(path) - return is_regex_magic(path) or #path -end - +-- group array-like table t to regex and length groups +local group_by_map = {} +local path_groups = {} +local function group_by_regex_or_length(t) + tb_clear(group_by_map) + tb_clear(path_groups) + local idx = 0 + for _, v in ipairs(t) do + local k = is_regex_magic(v) and 0 or #v + if group_by_map[k] then + tb_insert(path_groups[group_by_map[k]], v) --- group array-like table t by the function f, returning a table mapping from --- the result of invoking f on one of the elements to the actual elements. -local function group_by(t, f) - local result = {} - for _, value in ipairs(t) do - local key = f(value) - if result[key] then - tb_insert(result[key], value) else - result[key] = { value } + idx = idx + 1 + group_by_map[k] = idx + path_groups[idx] = { v } end end - return result + return path_groups, idx end --- split routes into multiple routes, one for each prefix length and one for all --- regular expressions -local function split_route_by_path_info(route_and_service, routes_and_services_split) - local original_route = route_and_service.route - - if is_empty_field(original_route.paths) or #original_route.paths == 1 or - not is_null(original_route.expression) -- expression will ignore paths - then - tb_insert(routes_and_services_split, route_and_service) - return - end - - -- make sure that route_and_service contains only the two expected entries, route and service - assert(tb_nkeys(route_and_service) == 1 or tb_nkeys(route_and_service) == 2) - local grouped_paths = group_by( - original_route.paths, sort_by_regex_or_length - ) - for index, paths in pairs(grouped_paths) do - local cloned_route = { - route = shallow_copy(original_route), - service = route_and_service.service, - } - - cloned_route.route.original_route = original_route - cloned_route.route.paths = paths - cloned_route.route.id = uuid_generator(original_route.id .. "#" .. tostring(index)) +-- split routes into multiple routes, +-- one for each prefix length and one for all regular expressions +local function split_routes_and_services_by_path(routes_and_services) + local count = #routes_and_services + for i = 1, count do + local route_and_service = routes_and_services[i] + local original_route = route_and_service.route + local original_paths = original_route.paths - tb_insert(routes_and_services_split, cloned_route) - end -end + if is_empty_field(original_paths) or #original_paths == 1 or + not is_null(original_route.expression) -- expression will ignore paths + then + goto continue + end + local grouped_paths, grouped_paths_count = group_by_regex_or_length(original_paths) + if grouped_paths_count == 1 then + goto continue + end -local function split_routes_and_services_by_path(routes_and_services) - local count = #routes_and_services - local routes_and_services_split = tb_new(count, 0) + -- make sure that route_and_service contains only + -- the two expected entries, route and service + local nkeys = tb_nkeys(route_and_service) + assert(nkeys == 1 or nkeys == 2) + + local original_route_id = original_route.id + local original_service = route_and_service.service + + for j = 1, grouped_paths_count do + local route = shallow_copy(original_route) + -- create a new route from the original route + route.original_route = original_route + route.paths = grouped_paths[j] + route.id = uuid_generator(original_route_id .. "#" .. j) + routes_and_services[j == 1 and i or (count + j - 1)] = { + route = route, + service = original_service, + } + end - for i = 1, count do - split_route_by_path_info(routes_and_services[i], routes_and_services_split) - end + ::continue:: + end -- for routes_and_services - return routes_and_services_split + return routes_and_services end @@ -753,4 +756,3 @@ return { split_routes_and_services_by_path = split_routes_and_services_by_path, } - diff --git a/spec/01-unit/08-router_spec.lua b/spec/01-unit/08-router_spec.lua index 558ed7ca792c..3f35b7c2408d 100644 --- a/spec/01-unit/08-router_spec.lua +++ b/spec/01-unit/08-router_spec.lua @@ -2,6 +2,7 @@ local Router local path_handling_tests = require "spec.fixtures.router_path_handling_tests" local uuid = require("kong.tools.utils").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 = { @@ -3663,7 +3664,8 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" } lazy_setup(function() - router = assert(new_router(use_case_routes)) + -- 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() @@ -3717,7 +3719,8 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }, } - local router = assert(new_router(use_case_routes)) + -- 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" }) @@ -4900,7 +4903,8 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }, } - router = assert(new_router(use_case)) + -- 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() @@ -4948,7 +4952,8 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }, } - router = assert(new_router(use_case)) + -- 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()