This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek 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 e9ff2d266 feat: add max_post_args_readable_size to bound post_arg.*
body reads (#13601)
e9ff2d266 is described below
commit e9ff2d266a9a57611b3ec15cf0d3c5216cfa2f39
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jun 29 11:01:32 2026 +0800
feat: add max_post_args_readable_size to bound post_arg.* body reads
(#13601)
---
apisix/cli/config.lua | 1 +
apisix/cli/schema.lua | 7 +++
apisix/core/ctx.lua | 26 +++++++-
conf/config.yaml.example | 2 +
docs/en/latest/router-radixtree.md | 9 +++
t/core/ctx3.t | 119 +++++++++++++++++++++++++++++++++++++
6 files changed, 163 insertions(+), 1 deletion(-)
diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index bdc22f107..9b8ef0ff1 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -47,6 +47,7 @@ local _M = {
},
delete_uri_tail_slash = false,
normalize_uri_like_servlet = false,
+ max_post_args_readable_size = 64,
router = {
http = "radixtree_host_uri",
ssl = "radixtree_sni"
diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua
index 6673e565a..b57b30fed 100644
--- a/apisix/cli/schema.lua
+++ b/apisix/cli/schema.lua
@@ -274,6 +274,13 @@ local config_schema = {
},
uniqueItems = true
},
+ max_post_args_readable_size = {
+ type = "integer",
+ minimum = 0,
+ default = 64,
+ description = "cap (in MB) on the request body read for
post_arg.* "
+ .. "route matching; 0 disables the limit",
+ },
}
},
nginx_config = {
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 1827254b6..64d109331 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -45,6 +45,8 @@ local pcall = pcall
local _M = {version = 0.2}
local GRAPHQL_DEFAULT_MAX_SIZE = 1048576 -- 1MiB
+local DEFAULT_MAX_POST_ARGS_SIZE = 64 -- 64MiB
+local MB = 1024 * 1024
local GRAPHQL_REQ_DATA_KEY = "query"
local GRAPHQL_REQ_METHOD_HTTP_GET = "GET"
local GRAPHQL_REQ_METHOD_HTTP_POST = "POST"
@@ -129,6 +131,27 @@ local function parse_graphql(ctx)
end
+-- read the cap (in bytes) for parsing post_arg.* bodies; 0 disables the limit
+local function get_max_post_args_readable_size()
+ local local_conf, err = config_local.local_conf()
+ if not local_conf then
+ log.error("failed to get local conf: ", err)
+ return DEFAULT_MAX_POST_ARGS_SIZE * MB
+ end
+
+ local size = core_tab.try_read_attr(local_conf, "apisix",
"max_post_args_readable_size")
+ if size == nil then
+ size = DEFAULT_MAX_POST_ARGS_SIZE
+ end
+
+ if size == 0 then
+ return nil
+ end
+
+ return size * MB
+end
+
+
local function get_parsed_graphql()
local ctx = ngx.ctx.api_ctx
if ctx._graphql then
@@ -324,7 +347,8 @@ do
elseif core_str.has_prefix(key, "post_arg.") then
-- trim the "post_arg." prefix (10 characters)
local arg_key = sub_str(key, 10)
- local parsed_body, err = request.get_request_body_table(t._ctx)
+ local max_size = get_max_post_args_readable_size()
+ local parsed_body, err =
request.get_request_body_table(t._ctx, nil, max_size)
if not parsed_body then
log.warn("failed to fetch post args value by key: ",
arg_key, " error: ", err)
return nil
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index efbf20d6b..f83b0e0f8 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -70,6 +70,8 @@ apisix:
delete_uri_tail_slash: false # Delete the '/' at the end of the URI
normalize_uri_like_servlet: false # If true, use the same path
normalization rules as the Java
# servlet specification. See
https://github.com/jakartaee/servlet/blob/master/spec/src/main/asciidoc/servlet-spec-body.adoc#352-uri-path-canonicalization,
which is used in Tomcat.
+ max_post_args_readable_size: 64 # Cap (in MB) on the request body read
when matching `post_arg.*`
+ # route predicates for JSON and
multipart requests. Set to 0 to disable the limit.
router:
http: radixtree_host_uri # radixtree_host_uri: match route by host and
URI
diff --git a/docs/en/latest/router-radixtree.md
b/docs/en/latest/router-radixtree.md
index c375360ef..48ff8b167 100644
--- a/docs/en/latest/router-radixtree.md
+++ b/docs/en/latest/router-radixtree.md
@@ -413,3 +413,12 @@ curl -X POST http://127.0.0.1:9180/_post \
}'
```
+
+:::note
+
+Matching `post_arg.*` against JSON or multipart bodies requires APISIX to read
and parse the
+request body during route matching. To avoid exhausting worker memory on large
bodies, the read
+is capped by `apisix.max_post_args_readable_size` in `config.yaml` (default
`64` MB). Bodies larger
+than this cap are not matched. Set it to `0` to disable the limit.
+
+:::
diff --git a/t/core/ctx3.t b/t/core/ctx3.t
index 058f91be6..7e24c62fb 100644
--- a/t/core/ctx3.t
+++ b/t/core/ctx3.t
@@ -227,3 +227,122 @@ hello world
before rewrite model: gpt-4
after rewrite model: claude
after rewrite temperature: 0.9
+
+
+
+=== TEST 7: set route matching on post_arg.model
+--- 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"
+ },
+ "uri": "/hello",
+ "vars": [
+ ["post_arg.model", "==", "gpt-4"]
+ ]
+ }]=]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 8: body over max_post_args_readable_size is not read, route not
matched
+--- yaml_config
+apisix:
+ node_listen: 1984
+ max_post_args_readable_size: 1
+--- request eval
+"POST /hello
+{\"model\":\"gpt-4\",\"pad\":\"" . ("a" x (2 * 1024 * 1024)) . "\"}"
+--- more_headers
+Content-Type: application/json
+--- error_code: 404
+--- response_body
+{"error_msg":"404 Route Not Found"}
+--- error_log
+is greater than the maximum size
+--- no_error_log
+[alert]
+
+
+
+=== TEST 9: body within max_post_args_readable_size still matches
+--- yaml_config
+apisix:
+ node_listen: 1984
+ max_post_args_readable_size: 1
+--- request
+POST /hello
+{"model":"gpt-4"}
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+
+
+
+=== TEST 10: body exactly at max_post_args_readable_size still matches
(boundary)
+--- yaml_config
+apisix:
+ node_listen: 1984
+ max_post_args_readable_size: 1
+--- request eval
+"POST /hello
+{\"model\":\"gpt-4\",\"pad\":\"" . ("a" x (1024 * 1024 - 26)) . "\"}"
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+
+
+
+=== TEST 11: oversized multipart body is not read, route not matched
+--- yaml_config
+apisix:
+ node_listen: 1984
+ max_post_args_readable_size: 1
+--- request eval
+"POST /hello\n" . "--boundary\r\nContent-Disposition: form-data;
name=\"model\"\r\n\r\ngpt-4\r\n--boundary\r\nContent-Disposition: form-data;
name=\"pad\"\r\n\r\n" . ("a" x (2 * 1024 * 1024)) . "\r\n--boundary--\r\n"
+--- more_headers
+Content-Type: multipart/form-data; boundary=boundary
+--- error_code: 404
+--- response_body
+{"error_msg":"404 Route Not Found"}
+--- error_log
+is greater than the maximum size
+--- no_error_log
+[alert]
+
+
+
+=== TEST 12: within-limit multipart body still matches
+--- yaml_config
+apisix:
+ node_listen: 1984
+ max_post_args_readable_size: 1
+--- request eval
+"POST /hello\n" . "--boundary\r\nContent-Disposition: form-data;
name=\"model\"\r\n\r\ngpt-4\r\n--boundary--\r\n"
+--- more_headers
+Content-Type: multipart/form-data; boundary=boundary
+--- response_body
+hello world
+--- error_code: 200