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

Reply via email to