Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apisix/cli/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions apisix/cli/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
26 changes: 25 additions & 1 deletion apisix/core/ctx.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions conf/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions docs/en/latest/router-radixtree.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

:::
69 changes: 69 additions & 0 deletions t/core/ctx3.t
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,72 @@ 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
Loading