diff --git a/apisix/core/table.lua b/apisix/core/table.lua index 60b0a6eba739..97c37f295500 100644 --- a/apisix/core/table.lua +++ b/apisix/core/table.lua @@ -31,6 +31,9 @@ local pairs = pairs local type = type local ngx_re = require("ngx.re") local isarray = require("table.isarray") +local str_byte = string.byte +local str_has_prefix = require("apisix.core.string").has_prefix +local DOT = str_byte(".") local _M = { @@ -120,6 +123,18 @@ end local deepcopy do + -- the members of `parent` are shallow-copied when `parent` is `prefix` + -- itself or one of its descendants. The match has to stop at a path + -- separator, otherwise a prefix like "self.value.plugins" would also + -- claim a sibling named "self.value.plugins2". + local function under_shallow_prefix(parent, prefix) + if not str_has_prefix(parent, prefix) then + return false + end + + return #parent == #prefix or str_byte(parent, #prefix + 1) == DOT + end + local function _deepcopy(orig, copied, parent, opts) -- If the array-like table contains nil in the middle, -- the len might be smaller than the expected. @@ -130,7 +145,9 @@ do copied[orig] = copy for orig_key, orig_value in pairs(orig) do local path = parent .. "." .. tostring(orig_key) - if opts and array_find(opts.shallows, path) then + if opts and (array_find(opts.shallows, path) or + (opts.shallow_prefix and + under_shallow_prefix(parent, opts.shallow_prefix))) then copy[orig_key] = orig_value else if type(orig_value) == "table" then diff --git a/apisix/plugin.lua b/apisix/plugin.lua index a2898bc0d404..563288495d15 100644 --- a/apisix/plugin.lua +++ b/apisix/plugin.lua @@ -818,7 +818,14 @@ local function merge_consumer_route(route_conf, consumer_conf, consumer_group_co return route_conf end - local new_route_conf = core.table.deepcopy(route_conf) + -- some plugins cache request-time state on their conf object (resolved DNS + -- nodes, probed backend versions, ...). Deep-copying the plugin confs would + -- hand every consumer its own copy and drop that state, so keep them shared + -- by reference, as they already are on the route without consumer auth. The + -- `plugins` container itself is still a fresh table, so the merge below + -- overwrites its keys without touching the original route conf. + local new_route_conf = core.table.deepcopy(route_conf, + { shallow_prefix = "self.value.plugins" }) if has_group_plugins then for name, conf in pairs(consumer_group_conf.value.plugins) do diff --git a/t/core/table.t b/t/core/table.t index 38616ae535bf..e1e4d6cfab38 100644 --- a/t/core/table.t +++ b/t/core/table.t @@ -359,3 +359,65 @@ tab_copied.a.b.c == tab.a.b.c1: true tab_copied.a.b.c == t1: true tab_copied.x.y == tab.x.y: false tab_copied.x.y == t2: false + + + +=== TEST 13: shallow_prefix +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local plugin1 = {name = "plugin1"} + local plugin2 = {name = "plugin2"} + local tab = { + plugins = { + p1 = plugin1, + p2 = plugin2, + }, + } + + local tab_copied = deepcopy(tab, { shallow_prefix = "self.plugins" }) + + tab_copied.plugins.p1 = {name = "plugin1_new_modified"} + tab_copied.plugins.p2.name = "plugin2_modified" + + ngx.say("table copied: ", require("toolkit.json").encode(tab_copied)) + ngx.say("table original: ", require("toolkit.json").encode(tab)) + } + } +--- request +GET /t +--- response_body +table copied: {"plugins":{"p1":{"name":"plugin1_new_modified"},"p2":{"name":"plugin2_modified"}}} +table original: {"plugins":{"p1":{"name":"plugin1"},"p2":{"name":"plugin2_modified"}}} +--- no_error_log +[error] + + + +=== TEST 14: shallow_prefix only matches at a path separator +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local tab = { + plugins = { p1 = {name = "p1"} }, + -- a sibling whose name starts with the prefix + plugins2 = { p2 = {name = "p2"} }, + } + + local tab_copied = deepcopy(tab, { shallow_prefix = "self.plugins" }) + + ngx.say("plugins.p1 shared: ", tab_copied.plugins.p1 == tab.plugins.p1) + ngx.say("plugins2.p2 shared: ", tab_copied.plugins2.p2 == tab.plugins2.p2) + } + } +--- request +GET /t +--- response_body +plugins.p1 shared: true +plugins2.p2 shared: false +--- no_error_log +[error]