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 5f028f3b7 perf(ctx): cache parsed request body to avoid repeated 
decode for post_arg.* (#13356)
5f028f3b7 is described below

commit 5f028f3b7898a0c4ed69d6c5cba91195d435b0f7
Author: AlinsRan <[email protected]>
AuthorDate: Wed May 13 12:59:30 2026 +0800

    perf(ctx): cache parsed request body to avoid repeated decode for 
post_arg.* (#13356)
---
 apisix/core/ctx.lua |  20 +++++++-
 apisix/patch.lua    |  27 +++++++++++
 t/core/ctx3.t       | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 174 insertions(+), 1 deletion(-)

diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 08833d4b5..1f97cb38f 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -174,7 +174,9 @@ local CONTENT_TYPE_JSON = "application/json"
 local CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
 local CONTENT_TYPE_MULTIPART_FORM = "multipart/form-data"
 
-local function get_parsed_request_body(ctx)
+local PARSED_BODY_CACHE_KEY = "_post_arg_request_body"
+
+local function _get_parsed_request_body(ctx)
     local ct_header = request.header(ctx, "Content-Type") or ""
 
     if core_str.find(ct_header, CONTENT_TYPE_JSON) then
@@ -210,6 +212,22 @@ local function get_parsed_request_body(ctx)
     return nil, err
 end
 
+-- Wrapper that caches the parsed body in ctx for the lifetime of the request.
+-- Errors are intentionally not cached: plugins may call 
ngx.req.set_body_data()
+-- in a later phase, so a transient read failure should not be frozen.
+local function get_parsed_request_body(ctx)
+    if ctx[PARSED_BODY_CACHE_KEY] ~= nil then
+        log.debug("reuse parsed request body from ctx cache")
+        return ctx[PARSED_BODY_CACHE_KEY]
+    end
+
+    local result, err = _get_parsed_request_body(ctx)
+    if result then
+        ctx[PARSED_BODY_CACHE_KEY] = result
+    end
+    return result, err
+end
+
 
 do
     local var_methods = {
diff --git a/apisix/patch.lua b/apisix/patch.lua
index 2b191b2a8..1e2303c64 100644
--- a/apisix/patch.lua
+++ b/apisix/patch.lua
@@ -31,6 +31,7 @@ local new_tab = require("table.new")
 local log = ngx.log
 local WARN = ngx.WARN
 local ipairs = ipairs
+local pairs = pairs
 local select = select
 local setmetatable = setmetatable
 local string = string
@@ -378,6 +379,32 @@ function _M.patch()
     ngx_socket.udp = function ()
         return patch_udp_socket(original_udp())
     end
+
+    -- Patch ngx.req.set_body_data to invalidate the parsed post_arg request 
body
+    -- cache in api_ctx. This ensures that post_arg.* variable lookups after a 
body
+    -- rewrite always reflect the new body content.
+    local _orig_set_body_data = ngx.req.set_body_data
+    ngx.req.set_body_data = function(data)
+        local api_ctx = ngx.ctx.api_ctx
+        if api_ctx and api_ctx._post_arg_request_body then
+            api_ctx._post_arg_request_body = nil
+            local var = api_ctx.var
+            local cache = var and var._cache
+            if cache then
+                local keys_to_clear = {}
+                for key in pairs(cache) do
+                    if type(key) == "string" and key:sub(1, 9) == "post_arg." 
then
+                        keys_to_clear[#keys_to_clear + 1] = key
+                    end
+                end
+
+                for i = 1, #keys_to_clear do
+                    cache[keys_to_clear[i]] = nil
+                end
+            end
+        end
+        return _orig_set_body_data(data)
+    end
 end
 
 
diff --git a/t/core/ctx3.t b/t/core/ctx3.t
index 69f305798..058f91be6 100644
--- a/t/core/ctx3.t
+++ b/t/core/ctx3.t
@@ -99,3 +99,131 @@ qr/serving ctx value from cache for key: graphql_name/
 serving ctx value from cache for key: graphql_name
 serving ctx value from cache for key: graphql_name
 serving ctx value from cache for key: graphql_name
+
+
+
+=== TEST 3: parse post body only once when multiple different post_arg.* keys 
are accessed
+--- 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,
+                 [=[{
+                        "methods": ["POST"],
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "plugins": {
+                            "serverless-post-function": {
+                                "phase": "rewrite",
+                                "functions" : ["return function(conf, ctx)
+                                                ngx.log(ngx.WARN, 'model: ', 
ctx.var['post_arg.model']);
+                                                ngx.log(ngx.WARN, 'stream: ', 
ctx.var['post_arg.stream']);
+                                                ngx.log(ngx.WARN, 
'temperature: ', tostring(ctx.var['post_arg.temperature']));
+                                                end"]
+                            }
+                        },
+                        "uri": "/hello",
+                        "vars": [
+                            ["post_arg.model", "==", "gpt-4"]
+                        ]
+                }]=]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 4: send request with multiple post_arg fields - body parsed only once
+--- request
+POST /hello
+{"model":"gpt-4","stream":true,"temperature":0.7}
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+--- error_log
+model: gpt-4
+stream: true
+temperature: 0.7
+--- grep_error_log eval
+qr/reuse parsed request body from ctx cache/
+--- grep_error_log_out
+reuse parsed request body from ctx cache
+reuse parsed request body from ctx cache
+
+
+
+=== TEST 5: set_body_data invalidates both body-level cache and post_arg.* 
variable cache
+--- 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,
+                 [=[{
+                        "methods": ["POST"],
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "plugins": {
+                            "serverless-pre-function": {
+                                "phase": "rewrite",
+                                "functions" : ["return function(conf, ctx)
+                                                -- first access: parse and 
cache body
+                                                local v1 = 
ctx.var['post_arg.model']
+                                                ngx.log(ngx.WARN, 'before 
rewrite model: ', tostring(v1));
+                                                -- rewrite body: must 
invalidate both caches
+                                                
ngx.req.set_body_data('{\"model\":\"claude\",\"temperature\":0.9}')
+                                                -- same key: must reflect new 
body, not stale cache
+                                                local v2 = 
ctx.var['post_arg.model']
+                                                ngx.log(ngx.WARN, 'after 
rewrite model: ', tostring(v2));
+                                                -- different key: must also 
reflect new body
+                                                local v3 = 
ctx.var['post_arg.temperature']
+                                                ngx.log(ngx.WARN, 'after 
rewrite temperature: ', tostring(v3));
+                                                end"]
+                            }
+                        },
+                        "uri": "/hello"
+                }]=]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 6: send request - set_body_data invalidation is reflected in 
post_arg.* access
+--- request
+POST /hello
+{"model":"gpt-4","temperature":0.1}
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+--- error_log
+before rewrite model: gpt-4
+after rewrite model: claude
+after rewrite temperature: 0.9

Reply via email to