This is an automated email from the ASF dual-hosted git repository.
AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 377cb5961 perf(request-id): read only the configured header and encode
v4 ids through a table (#14140)
377cb5961 is described below
commit 377cb5961f4de4c9e1993dee605d7652ebb0f071
Author: AlinsRan <[email protected]>
AuthorDate: Thu Nov 5 17:19:52 2026 +0800
perf(request-id): read only the configured header and encode v4 ids through
a table (#14140)
---
apisix/core/request.lua | 2 +-
apisix/core/utils.lua | 31 +++++
apisix/plugins/request-id.lua | 24 ++--
t/plugin/request-id.t | 291 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 339 insertions(+), 9 deletions(-)
diff --git a/apisix/core/request.lua b/apisix/core/request.lua
index 950ef1a8d..66a03a3a6 100644
--- a/apisix/core/request.lua
+++ b/apisix/core/request.lua
@@ -146,7 +146,7 @@ local function modify_header(ctx, header_name,
header_value, override)
-- after 'header_value', but the style is too ugly!
header_value = header_name
header_name = ctx
- ctx = nil
+ ctx = ngx.ctx.api_ctx
if override then
log.warn("DEPRECATED: use set_header(ctx, header_name,
header_value) instead")
diff --git a/apisix/core/utils.lua b/apisix/core/utils.lua
index de199ff2d..fd488047c 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -591,6 +591,37 @@ function _M.set_var_rate_limiting_info(ctx, key, limit,
remaining, reset, detail
end
+local hex_byte = {}
+for i = 0, 255 do
+ hex_byte[i] = str_format("%02x", i)
+end
+
+
+local function random_hex_byte()
+ return hex_byte[math_random(0, 255)]
+end
+
+
+---
+-- Generate a random UUID v4 string.
+--
+-- Draws the same bytes in the same order as resty.jit-uuid's generate_v4, so
+-- a given seed yields the same id, but encodes them through a lookup table.
+--
+-- @function core.utils.generate_uuid_v4
+-- @treturn string UUID v4 string, e.g. "1f3a9c2e-7b4d-4e8f-9a6b-2c1d0e5f7a3b"
+function _M.generate_uuid_v4()
+ return random_hex_byte() .. random_hex_byte() .. random_hex_byte()
+ .. random_hex_byte() .. "-" .. random_hex_byte() ..
random_hex_byte() .. "-"
+ .. hex_byte[bit.bor(bit.band(math_random(0, 255), 0x0F), 0x40)]
+ .. random_hex_byte() .. "-"
+ .. hex_byte[bit.bor(bit.band(math_random(0, 255), 0x3F), 0x80)]
+ .. random_hex_byte() .. "-" .. random_hex_byte() ..
random_hex_byte()
+ .. random_hex_byte() .. random_hex_byte() .. random_hex_byte()
+ .. random_hex_byte()
+end
+
+
-- worker-local monotonic state for UUID v7 (no shared dict needed)
-- Each nginx worker has its own copy; no locking required.
local _v7_last_ms = 0
diff --git a/apisix/plugins/request-id.lua b/apisix/plugins/request-id.lua
index 7036853e7..3007d0915 100644
--- a/apisix/plugins/request-id.lua
+++ b/apisix/plugins/request-id.lua
@@ -17,10 +17,10 @@
local ngx = ngx
local core = require("apisix.core")
-local uuid = require("resty.jit-uuid")
local resty_random = require("resty.random")
local ksuid = require("resty.ksuid")
local math_random = math.random
+local type = type
local str_byte = string.byte
local str_sub = string.sub
local table_concat = table.concat
@@ -101,7 +101,7 @@ end
local function get_request_id(conf)
if conf.algorithm == "uuid" then
- return uuid()
+ return core.utils.generate_uuid_v4()
end
if conf.algorithm == "uuidv7" then
return core.utils.generate_uuid_v7()
@@ -118,19 +118,21 @@ local function get_request_id(conf)
return ksuid.generate()
end
- return uuid()
+ return core.utils.generate_uuid_v4()
end
function _M.rewrite(conf, ctx)
- local headers = ngx.req.get_headers()
local uuid_val
- local header_req_id = headers[conf.header_name]
+ local header_req_id = core.request.headers(ctx)[conf.header_name]
if not header_req_id or header_req_id == "" then
uuid_val = get_request_id(conf)
core.request.set_header(ctx, conf.header_name, uuid_val)
+ elseif type(header_req_id) == "table" then
+ -- the cached header table is updated in place by later header changes
+ uuid_val = core.table.clone(header_req_id)
else
- uuid_val = headers[conf.header_name]
+ uuid_val = header_req_id
end
if conf.include_in_response then
@@ -146,8 +148,14 @@ function _M.header_filter(conf, ctx)
return
end
- local headers = ngx.resp.get_headers()
- local header_req_id = headers[conf.header_name]
+ local header_req_id
+ if core.string.find(conf.header_name, "_") then
+ -- unlike ngx.header, the table from ngx.resp.get_headers() maps "_" to
+ -- "-" even if lua_transform_underscores_in_response_headers is off
+ header_req_id = ngx.resp.get_headers()[conf.header_name]
+ else
+ header_req_id = ngx.header[conf.header_name]
+ end
if not header_req_id or header_req_id == "" then
core.response.set_header(conf.header_name, ctx["request-id-" ..
conf.header_name])
end
diff --git a/t/plugin/request-id.t b/t/plugin/request-id.t
index 8a89404bc..46a9370fa 100644
--- a/t/plugin/request-id.t
+++ b/t/plugin/request-id.t
@@ -1089,3 +1089,294 @@ ok
--- timeout: 30
--- response_body
true
+
+
+
+=== TEST 29: a repeated request id header is echoed back with every value
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port ..
"/opentracing"
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri, {
+ headers = {["X-Request-Id"] = {"id-a", "id-b"}},
+ })
+ if not res then
+ ngx.say("request failed: ", err)
+ return
+ end
+ local id = res.headers["X-Request-Id"]
+ if type(id) == "table" then
+ ngx.say(table.concat(id, ","))
+ else
+ ngx.say(tostring(id))
+ end
+ }
+ }
+--- response_body
+id-a,id-b
+
+
+
+=== TEST 30: route to an upstream that sets the request id header itself
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "request-id": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/headers"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 31: the upstream's response header is kept, not replaced by the
generated id
+--- request
+GET /headers?X-Request-Id=from-upstream
+--- response_headers
+X-Request-Id: from-upstream
+
+
+
+=== TEST 32: a header added later by proxy-rewrite is not echoed with a
repeated request id
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local http = require "resty.http"
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "request-id": {},
+ "proxy-rewrite": {
+ "headers": {
+ "add": {
+ "X-Request-Id": "from-rewrite"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/opentracing"
+ }]]
+ )
+ if code >= 300 then
+ ngx.say(body)
+ return
+ end
+ ngx.sleep(0.5)
+
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port ..
"/opentracing"
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri, {
+ headers = {["X-Request-Id"] = {"id-a", "id-b"}},
+ })
+ if not res then
+ ngx.say("request failed: ", err)
+ return
+ end
+ local id = res.headers["X-Request-Id"]
+ if type(id) == "table" then
+ ngx.say(table.concat(id, ","))
+ else
+ ngx.say(tostring(id))
+ end
+ }
+ }
+--- response_body
+id-a,id-b
+
+
+
+=== TEST 33: route to an upstream that sets the header, configured with
underscores
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "request-id": {
+ "header_name": "X_Request_Id"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/headers"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 34: an underscore header name still matches the upstream header
without the transform
+--- http_config
+lua_transform_underscores_in_response_headers off;
+--- config
+ location /t {
+ content_by_lua_block {
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:connect("127.0.0.1", ngx.var.server_port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+ sock:send("GET /headers?X-Request-Id=from-upstream HTTP/1.0\r\n"
+ .. "Host: localhost\r\n\r\n")
+ local resp = sock:receive("*a")
+ sock:close()
+ local upstream_id = ngx.re.find(resp, [[\nX-Request-Id:
from-upstream\r]], "jo")
+ local generated_id = ngx.re.find(resp, [[\nX_Request_Id:]], "joi")
+ ngx.say("upstream id: ", upstream_id ~= nil)
+ ngx.say("generated id: ", generated_id ~= nil)
+ }
+ }
+--- response_body
+upstream id: true
+generated id: false
+
+
+
+=== TEST 35: generate_uuid_v4 yields the same ids as resty.jit-uuid for a seed
+--- config
+ location /t {
+ content_by_lua_block {
+ local jit_uuid = require("resty.jit-uuid")
+ local core = require("apisix.core")
+ -- apisix/patch.lua turns later math.randomseed calls into no-ops
+ local randomseed
+ for i = 1, math.huge do
+ local name, val = debug.getupvalue(math.randomseed, i)
+ if not name then
+ break
+ end
+ if name == "math_randomseed" then
+ randomseed = val
+ end
+ end
+ for seed = 1, 10000 do
+ randomseed(seed)
+ local expected = jit_uuid.generate_v4()
+ local expected_next = math.random()
+ randomseed(seed)
+ local got = core.utils.generate_uuid_v4()
+ if got ~= expected or math.random() ~= expected_next then
+ ngx.say("seed ", seed, ": ", got, " ~= ", expected)
+ return
+ end
+ end
+ ngx.say("passed")
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 36: a global rule sets the request id through the setter without ctx
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "request-id": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/opentracing"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ code, body = t('/apisix/admin/global_rules/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": ["return function(conf, ctx)
local core = require('apisix.core'); core.request.headers(ctx);
core.request.set_header('X-Request-Id', 'from-global-rule') end"]
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 37: the request id set without ctx is the one echoed back
+--- request
+GET /opentracing
+--- more_headers
+X-Request-Id: from-client
+--- response_headers
+X-Request-Id: from-global-rule
+
+
+
+=== TEST 38: delete the global rule
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_DELETE)
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed