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 070853d6a feat: bound request and response body buffering in plugins
(#13705)
070853d6a is described below
commit 070853d6a1c98b4c2e69c502a0ed577c3d5570bc
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Thu Jul 23 11:38:08 2026 +0800
feat: bound request and response body buffering in plugins (#13705)
---
apisix/plugins/ai-prompt-decorator.lua | 13 +++++--
apisix/plugins/ai-prompt-guard.lua | 9 ++++-
apisix/plugins/ai-prompt-template.lua | 13 +++++--
apisix/plugins/ai-proxy/schema.lua | 4 +-
apisix/plugins/ai-request-rewrite.lua | 9 ++++-
apisix/plugins/authz-keycloak.lua | 9 ++++-
apisix/plugins/body-transformer.lua | 22 ++++++++++-
apisix/plugins/cas-auth.lua | 9 ++++-
apisix/plugins/degraphql.lua | 11 +++++-
apisix/plugins/grpc-transcode.lua | 10 ++++-
apisix/plugins/grpc-transcode/response.lua | 5 ++-
apisix/plugins/grpc-web.lua | 9 ++++-
apisix/plugins/http-dubbo.lua | 26 +++++++++++--
apisix/plugins/oas-validator.lua | 9 ++++-
apisix/plugins/openwhisk.lua | 14 ++++++-
apisix/plugins/proxy-cache/init.lua | 8 ++++
apisix/plugins/proxy-cache/memory_handler.lua | 9 ++++-
apisix/plugins/request-validation.lua | 9 ++++-
apisix/plugins/response-rewrite.lua | 10 ++++-
apisix/plugins/serverless/generic-upstream.lua | 9 ++++-
docs/en/latest/plugins/ai-prompt-decorator.md | 1 +
docs/en/latest/plugins/ai-prompt-guard.md | 1 +
docs/en/latest/plugins/ai-prompt-template.md | 1 +
docs/en/latest/plugins/ai-request-rewrite.md | 1 +
docs/en/latest/plugins/authz-keycloak.md | 1 +
docs/en/latest/plugins/aws-lambda.md | 1 +
docs/en/latest/plugins/azure-functions.md | 1 +
docs/en/latest/plugins/body-transformer.md | 2 +
docs/en/latest/plugins/cas-auth.md | 1 +
docs/en/latest/plugins/degraphql.md | 1 +
docs/en/latest/plugins/grpc-transcode.md | 1 +
docs/en/latest/plugins/grpc-web.md | 1 +
docs/en/latest/plugins/http-dubbo.md | 1 +
docs/en/latest/plugins/oas-validator.md | 1 +
docs/en/latest/plugins/openfunction.md | 1 +
docs/en/latest/plugins/openwhisk.md | 1 +
docs/en/latest/plugins/proxy-cache.md | 1 +
docs/en/latest/plugins/request-validation.md | 1 +
docs/en/latest/plugins/response-rewrite.md | 1 +
t/plugin/request-validation.t | 53 ++++++++++++++++++++++++++
t/plugin/response-rewrite.t | 48 +++++++++++++++++++++++
41 files changed, 309 insertions(+), 29 deletions(-)
diff --git a/apisix/plugins/ai-prompt-decorator.lua
b/apisix/plugins/ai-prompt-decorator.lua
index ba9cbe6a5..a48cd7deb 100644
--- a/apisix/plugins/ai-prompt-decorator.lua
+++ b/apisix/plugins/ai-prompt-decorator.lua
@@ -40,6 +40,13 @@ local prompts = {
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
prepend = prompts,
append = prompts,
},
@@ -64,8 +71,8 @@ function _M.check_schema(conf)
end
-local function get_request_body_table()
- local body, err = core.request.get_body()
+local function get_request_body_table(max_size)
+ local body, err = core.request.get_body(max_size)
if not body then
return nil, { message = "could not get body: " .. (err or "request
body is empty") }
end
@@ -80,7 +87,7 @@ end
function _M.rewrite(conf, ctx)
- local body_tab, err = get_request_body_table()
+ local body_tab, err = get_request_body_table(conf.max_req_body_size)
if not body_tab then
return 400, err
end
diff --git a/apisix/plugins/ai-prompt-guard.lua
b/apisix/plugins/ai-prompt-guard.lua
index 56cec2f8a..909c6bf04 100644
--- a/apisix/plugins/ai-prompt-guard.lua
+++ b/apisix/plugins/ai-prompt-guard.lua
@@ -28,6 +28,13 @@ local plugin_name = "ai-prompt-guard"
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
match_all_roles = {
type = "boolean",
default = false,
@@ -98,7 +105,7 @@ end
function _M.access(conf, ctx)
- local body = core.request.get_body()
+ local body = core.request.get_body(conf.max_req_body_size)
if not body then
core.log.error("Empty request body")
return 400, {message = "Empty request body"}
diff --git a/apisix/plugins/ai-prompt-template.lua
b/apisix/plugins/ai-prompt-template.lua
index 0fe47617e..2c9451be8 100644
--- a/apisix/plugins/ai-prompt-template.lua
+++ b/apisix/plugins/ai-prompt-template.lua
@@ -41,6 +41,13 @@ local prompts = {
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
templates = {
type = "array",
minItems = 1,
@@ -90,8 +97,8 @@ function _M.check_schema(conf)
end
-local function get_request_body_table()
- local body, err = core.request.get_body()
+local function get_request_body_table(max_size)
+ local body, err = core.request.get_body(max_size)
if not body then
return nil, { message = "could not get body: " .. (err or "request
body is empty") }
end
@@ -115,7 +122,7 @@ local function find_template(conf, template_name)
end
function _M.rewrite(conf, ctx)
- local body_tab, err = get_request_body_table()
+ local body_tab, err = get_request_body_table(conf.max_req_body_size)
if not body_tab then
return 400, err
end
diff --git a/apisix/plugins/ai-proxy/schema.lua
b/apisix/plugins/ai-proxy/schema.lua
index 5ffd6ef85..a7833f0c1 100644
--- a/apisix/plugins/ai-proxy/schema.lua
+++ b/apisix/plugins/ai-proxy/schema.lua
@@ -248,7 +248,7 @@ _M.ai_proxy_schema = {
minimum = 1,
default = 67108864,
description = "maximum request body size in bytes the plugin reads
"
- .. "into memory; larger requests are rejected with 413.
"
+ .. "into memory; larger request bodies are rejected. "
.. "Prevents unbounded memory buffering of large
bodies.",
},
max_stream_duration_ms = {
@@ -373,7 +373,7 @@ _M.ai_proxy_multi_schema = {
minimum = 1,
default = 67108864,
description = "maximum request body size in bytes the plugin reads
"
- .. "into memory; larger requests are rejected with 413.
"
+ .. "into memory; larger request bodies are rejected. "
.. "Prevents unbounded memory buffering of large
bodies.",
},
max_stream_duration_ms = {
diff --git a/apisix/plugins/ai-request-rewrite.lua
b/apisix/plugins/ai-request-rewrite.lua
index 5bef20771..a2b9b41fd 100644
--- a/apisix/plugins/ai-request-rewrite.lua
+++ b/apisix/plugins/ai-request-rewrite.lua
@@ -60,6 +60,13 @@ local model_options_schema = {
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
prompt = {
type = "string",
description = "The prompt to rewrite client request."
@@ -172,7 +179,7 @@ function _M.check_schema(conf)
end
function _M.access(conf, ctx)
- local client_request_body, err = core.request.get_body()
+ local client_request_body, err =
core.request.get_body(conf.max_req_body_size)
if err then
core.log.warn("failed to get request body: ", err)
return HTTP_BAD_REQUEST
diff --git a/apisix/plugins/authz-keycloak.lua
b/apisix/plugins/authz-keycloak.lua
index b75f334f7..c1ea88723 100644
--- a/apisix/plugins/authz-keycloak.lua
+++ b/apisix/plugins/authz-keycloak.lua
@@ -27,6 +27,13 @@ local pairs = pairs
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
discovery = {type = "string", minLength = 1, maxLength = 4096},
token_endpoint = {type = "string", minLength = 1, maxLength = 4096},
resource_registration_endpoint = {type = "string", minLength = 1,
maxLength = 4096},
@@ -694,7 +701,7 @@ end
local function generate_token_using_password_grant(conf,ctx)
log.debug("generate_token_using_password_grant Function Called")
- local body, err = core.request.get_body()
+ local body, err = core.request.get_body(conf.max_req_body_size)
if err or not body then
log.error("Failed to get request body: ", err)
return 503
diff --git a/apisix/plugins/body-transformer.lua
b/apisix/plugins/body-transformer.lua
index b0debcbea..fefc6221d 100644
--- a/apisix/plugins/body-transformer.lua
+++ b/apisix/plugins/body-transformer.lua
@@ -48,6 +48,20 @@ local schema = {
properties = {
request = transform_schema,
response = transform_schema,
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
+ max_resp_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum response body size in bytes buffered into "
+ .. "memory; larger responses are truncated",
+ },
},
anyOf = {
{required = {"request"}},
@@ -233,7 +247,11 @@ function _M.rewrite(conf, ctx)
local request_method = ngx.var.request_method
conf = core.table.deepcopy(conf)
ctx.body_transformer_conf = conf
- local body = core.request.get_body()
+ local body, err = core.request.get_body(conf.max_req_body_size)
+ if err then
+ core.log.error("failed to get request body: ", err)
+ return 413
+ end
set_input_format(conf, "request", ctx.var.http_content_type,
request_method)
local out, status, err = transform(conf, body, "request", ctx,
request_method)
if not out then
@@ -262,7 +280,7 @@ function _M.body_filter(_, ctx)
return
end
if conf.response then
- local body = core.response.hold_body_chunk(ctx)
+ local body = core.response.hold_body_chunk(ctx, false,
conf.max_resp_body_size)
if ngx.arg[2] == false and not body then
return
end
diff --git a/apisix/plugins/cas-auth.lua b/apisix/plugins/cas-auth.lua
index f179c9f92..dfc166037 100644
--- a/apisix/plugins/cas-auth.lua
+++ b/apisix/plugins/cas-auth.lua
@@ -40,6 +40,13 @@ local plugin_name = "cas-auth"
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
idp_uri = {type = "string"},
cas_callback_uri = {
type = "string",
@@ -358,7 +365,7 @@ function _M.access(conf, ctx)
end
if method == "POST" and uri == cas_callback_path then
- local data = core.request.get_body()
+ local data = core.request.get_body(conf.max_req_body_size)
local ticket = data and
data:match("<samlp:SessionIndex>(.+)</samlp:SessionIndex>")
if ticket == nil then
return ngx.HTTP_BAD_REQUEST,
diff --git a/apisix/plugins/degraphql.lua b/apisix/plugins/degraphql.lua
index e47a276a8..f7e2b5b0f 100644
--- a/apisix/plugins/degraphql.lua
+++ b/apisix/plugins/degraphql.lua
@@ -25,6 +25,13 @@ local type = type
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
query = {
type = "string",
minLength = 1,
@@ -75,7 +82,7 @@ end
local function fetch_post_variables(conf)
- local req_body, err = core.request.get_body()
+ local req_body, err = core.request.get_body(conf.max_req_body_size)
if err ~= nil then
core.log.error("failed to get request body: ", err)
return nil, 503
@@ -146,7 +153,7 @@ function _M.access(conf, ctx)
if meth == "POST" then
if not conf.variables then
-- the set_body_data requires to read the body first
- core.request.get_body()
+ core.request.get_body(conf.max_req_body_size)
end
core.request.set_header(ctx, "Content-Type", "application/json")
diff --git a/apisix/plugins/grpc-transcode.lua
b/apisix/plugins/grpc-transcode.lua
index 625018fb7..3b5d58046 100644
--- a/apisix/plugins/grpc-transcode.lua
+++ b/apisix/plugins/grpc-transcode.lua
@@ -47,6 +47,13 @@ local pb_option_def = {
local schema = {
type = "object",
properties = {
+ max_resp_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum response body size in bytes buffered into "
+ .. "memory for transcoding; larger responses are
truncated",
+ },
proto_id = schema_def.id_schema,
service = {
description = "the grpc service name",
@@ -200,7 +207,8 @@ function _M.body_filter(conf, ctx)
end
local err = response(ctx, proto_obj, conf.service, conf.method,
conf.pb_option,
- conf.show_status_in_body, conf.status_detail_type)
+ conf.show_status_in_body, conf.status_detail_type,
+ conf.max_resp_body_size)
if err then
core.log.error("transform response error: ", err)
return
diff --git a/apisix/plugins/grpc-transcode/response.lua
b/apisix/plugins/grpc-transcode/response.lua
index 9dd6780f0..fd8dbfa6c 100644
--- a/apisix/plugins/grpc-transcode/response.lua
+++ b/apisix/plugins/grpc-transcode/response.lua
@@ -93,8 +93,9 @@ local function handle_error_response(status_detail_type,
proto)
end
-return function(ctx, proto, service, method, pb_option, show_status_in_body,
status_detail_type)
- local buffer = core.response.hold_body_chunk(ctx)
+return function(ctx, proto, service, method, pb_option, show_status_in_body,
status_detail_type,
+ max_resp_body_size)
+ local buffer = core.response.hold_body_chunk(ctx, false,
max_resp_body_size)
if not buffer then
return nil
end
diff --git a/apisix/plugins/grpc-web.lua b/apisix/plugins/grpc-web.lua
index da059822e..8ca0c3f88 100644
--- a/apisix/plugins/grpc-web.lua
+++ b/apisix/plugins/grpc-web.lua
@@ -41,6 +41,13 @@ local plugin_name = "grpc-web"
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
cors_allow_headers = {
description =
"multiple header use ',' to split. default:
content-type,x-grpc-web,x-user-agent.",
@@ -143,7 +150,7 @@ function _M.access(conf, ctx)
end
-- set grpc body
- local body, err = core.request.get_body()
+ local body, err = core.request.get_body(conf.max_req_body_size)
if err or not body then
core.log.error("failed to read request body, err: ", err)
return exit(ctx, 400)
diff --git a/apisix/plugins/http-dubbo.lua b/apisix/plugins/http-dubbo.lua
index f068654ba..c4be4c19c 100644
--- a/apisix/plugins/http-dubbo.lua
+++ b/apisix/plugins/http-dubbo.lua
@@ -32,6 +32,13 @@ local plugin_name = "http-dubbo"
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
service_name = {
type = "string",
minLength = 1,
@@ -174,7 +181,12 @@ local function get_dubbo_request(conf, ctx)
serialized = serialization_header == "true"
end
if serialized then
- params = core.request.get_body()
+ local body, err = core.request.get_body(conf.max_req_body_size)
+ if err then
+ core.log.error("failed to get request body: ", err)
+ return nil, 413
+ end
+ params = body
if params then
local end_of_params = core.string.sub(params, -1)
if end_of_params ~= "\n" then
@@ -182,7 +194,11 @@ local function get_dubbo_request(conf, ctx)
end
end
else
- local body_data = core.request.get_body()
+ local body_data, err = core.request.get_body(conf.max_req_body_size)
+ if err then
+ core.log.error("failed to get request body: ", err)
+ return nil, 413
+ end
if body_data then
local lua_object = core.json.decode(body_data);
for _, v in pairs(lua_object) do
@@ -231,7 +247,11 @@ function _M.before_proxy(conf, ctx)
core.log.error("failed to connect to upstream ", err)
return 502
end
- local request = get_dubbo_request(conf, ctx)
+ local request, err_code = get_dubbo_request(conf, ctx)
+ if not request then
+ sock:close()
+ return err_code
+ end
local bytes, _ = sock:send(request)
if bytes > 0 then
local header, _ = sock:receiveany(16);
diff --git a/apisix/plugins/oas-validator.lua b/apisix/plugins/oas-validator.lua
index 0cdd5b4a0..602585d5d 100644
--- a/apisix/plugins/oas-validator.lua
+++ b/apisix/plugins/oas-validator.lua
@@ -35,6 +35,13 @@ local DEFAULT_SPEC_URL_TTL = 3600
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
spec = {
description = "schema against which the request/response will be
validated",
type = "string",
@@ -244,7 +251,7 @@ function _M.access(conf, ctx)
local req_body
if not conf.skip_request_body_validation then
- local body, body_err = core.request.get_body()
+ local body, body_err = core.request.get_body(conf.max_req_body_size)
if body_err ~= nil then
core.log.error("failed reading request body, err: " .. body_err)
return 500, {message = "error reading the request body. err: " ..
body_err}
diff --git a/apisix/plugins/openwhisk.lua b/apisix/plugins/openwhisk.lua
index f1399254c..035bc61f3 100644
--- a/apisix/plugins/openwhisk.lua
+++ b/apisix/plugins/openwhisk.lua
@@ -25,6 +25,13 @@ local name_pattern = [[\A([\w]|[\w][\w@ .-]*[\[email protected]]+)\z]]
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
api_host = {type = "string"},
ssl_verify = {
type = "boolean",
@@ -77,9 +84,14 @@ end
function _M.access(conf, ctx)
+ local body, err = core.request.get_body(conf.max_req_body_size)
+ if err then
+ core.log.error("failed to get request body: ", err)
+ return 413
+ end
local params = {
method = "POST",
- body = core.request.get_body(),
+ body = body,
query = {
blocking = "true",
result = tostring(conf.result),
diff --git a/apisix/plugins/proxy-cache/init.lua
b/apisix/plugins/proxy-cache/init.lua
index 3ccacf7cd..cf23b05bd 100644
--- a/apisix/plugins/proxy-cache/init.lua
+++ b/apisix/plugins/proxy-cache/init.lua
@@ -50,6 +50,14 @@ end
local schema = {
type = "object",
properties = {
+ max_resp_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum response body size in bytes buffered into "
+ .. "memory by the memory cache strategy; larger "
+ .. "responses are streamed through without being
cached",
+ },
cache_zone = {
type = "string",
minLength = 1,
diff --git a/apisix/plugins/proxy-cache/memory_handler.lua
b/apisix/plugins/proxy-cache/memory_handler.lua
index cba8febef..95599cff3 100644
--- a/apisix/plugins/proxy-cache/memory_handler.lua
+++ b/apisix/plugins/proxy-cache/memory_handler.lua
@@ -525,11 +525,18 @@ function _M.body_filter(conf, ctx)
return
end
- local res_body = core.response.hold_body_chunk(ctx, true)
+ local res_body = core.response.hold_body_chunk(ctx, true,
conf.max_resp_body_size)
if not res_body then
return
end
+ if conf.max_resp_body_size and #res_body >= conf.max_resp_body_size then
+ -- response exceeds the buffering cap; stream it through without
+ -- caching so we never store a truncated body
+ ctx.cache = nil
+ return
+ end
+
local entry = {
status = ngx.status,
body = res_body,
diff --git a/apisix/plugins/request-validation.lua
b/apisix/plugins/request-validation.lua
index 02f2f51c3..d94a72810 100644
--- a/apisix/plugins/request-validation.lua
+++ b/apisix/plugins/request-validation.lua
@@ -25,6 +25,13 @@ local schema = {
properties = {
header_schema = {type = "object"},
body_schema = {type = "object"},
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered into "
+ .. "memory; larger request bodies are rejected",
+ },
rejected_code = {type = "integer", minimum = 200, maximum = 599,
default = 400},
rejected_msg = {type = "string", minLength = 1, maxLength = 256}
},
@@ -81,7 +88,7 @@ function _M.rewrite(conf, ctx)
if conf.body_schema then
local req_body
- local body, err = core.request.get_body()
+ local body, err = core.request.get_body(conf.max_req_body_size)
if not body then
if err then
core.log.error("failed to get body: ", err)
diff --git a/apisix/plugins/response-rewrite.lua
b/apisix/plugins/response-rewrite.lua
index b86604a36..df4fa889d 100644
--- a/apisix/plugins/response-rewrite.lua
+++ b/apisix/plugins/response-rewrite.lua
@@ -38,6 +38,14 @@ local lrucache = core.lrucache.new({
local schema = {
type = "object",
properties = {
+ max_resp_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum response body size in bytes buffered into "
+ .. "memory when filters are applied; larger responses "
+ .. "are truncated",
+ },
headers = {
description = "new headers for response",
anyOf = {
@@ -257,7 +265,7 @@ function _M.body_filter(conf, ctx)
if conf.filters then
- local body = core.response.hold_body_chunk(ctx)
+ local body = core.response.hold_body_chunk(ctx, false,
conf.max_resp_body_size)
if not body then
return
end
diff --git a/apisix/plugins/serverless/generic-upstream.lua
b/apisix/plugins/serverless/generic-upstream.lua
index 52a0cb3ea..5c3a6c587 100644
--- a/apisix/plugins/serverless/generic-upstream.lua
+++ b/apisix/plugins/serverless/generic-upstream.lua
@@ -32,6 +32,13 @@ return function(plugin_name, version, priority,
request_processor, authz_schema,
local schema = {
type = "object",
properties = {
+ max_req_body_size = {
+ type = "integer",
+ minimum = 1,
+ default = 67108864,
+ description = "maximum request body size in bytes buffered
into "
+ .. "memory; larger request bodies are rejected",
+ },
function_uri = {type = "string"},
authorization = authz_schema,
timeout = {type = "integer", minimum = 100, default = 3000},
@@ -62,7 +69,7 @@ return function(plugin_name, version, priority,
request_processor, authz_schema,
local uri_args = core.request.get_uri_args(ctx)
local headers = core.request.headers(ctx) or {}
- local req_body, err = core.request.get_body()
+ local req_body, err = core.request.get_body(conf.max_req_body_size)
if err then
core.log.error("error while reading request body: ", err)
diff --git a/docs/en/latest/plugins/ai-prompt-decorator.md
b/docs/en/latest/plugins/ai-prompt-decorator.md
index 454392278..ae351dd11 100644
--- a/docs/en/latest/plugins/ai-prompt-decorator.md
+++ b/docs/en/latest/plugins/ai-prompt-decorator.md
@@ -42,6 +42,7 @@ The `ai-prompt-decorator` Plugin modifies user input prompts
by prefixing and ap
| Name | Type | Required | Default | Valid values | Description |
| --- | --- | --- | --- | --- | --- |
+| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request
body size in bytes buffered into memory. Requests with a larger body are
rejected. |
| `prepend` | array | Conditionally\* | | | An array of prompt objects to be
prepended. |
| `prepend.role` | string | True | | [`system`, `user`, `assistant`] | Role of
the message. |
| `prepend.content` | string | True | | length >= 1 | Content of the message
(prompt). |
diff --git a/docs/en/latest/plugins/ai-prompt-guard.md
b/docs/en/latest/plugins/ai-prompt-guard.md
index b5ebd17cc..7950fd3a5 100644
--- a/docs/en/latest/plugins/ai-prompt-guard.md
+++ b/docs/en/latest/plugins/ai-prompt-guard.md
@@ -44,6 +44,7 @@ When both `allow_patterns` and `deny_patterns` are
configured, the Plugin first
| Name | Type | Required | Default | Valid values | Description |
| --- | --- | --- | --- | --- | --- |
+| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request
body size in bytes buffered into memory. Requests with a larger body are
rejected. |
| `match_all_roles` | boolean | False | false | | If `true`, validate messages
from all roles. If `false`, validate the message from `user` role only. |
| `match_all_conversation_history` | boolean | False | false | | If `true`,
concatenate and check all messages in the conversation history. If `false`,
only check the content of the last message. |
| `allow_patterns` | array | False | [] | | An array of regex patterns that
messages should match. When configured, messages must match at least one
pattern to be considered valid. |
diff --git a/docs/en/latest/plugins/ai-prompt-template.md
b/docs/en/latest/plugins/ai-prompt-template.md
index 2d3e80de8..b176aec94 100644
--- a/docs/en/latest/plugins/ai-prompt-template.md
+++ b/docs/en/latest/plugins/ai-prompt-template.md
@@ -42,6 +42,7 @@ The `ai-prompt-template` Plugin supports pre-configuring
prompt templates that o
| Name | Type | Required | Default | Valid values | Description |
| --- | --- | --- | --- | --- | --- |
+| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request
body size in bytes buffered into memory. Requests with a larger body are
rejected. |
| `templates` | array | True | | | An array of template objects. |
| `templates.name` | string | True | | | Name of the template. When requesting
the Route, the request should include the template name that corresponds to the
configured template. |
| `templates.template` | object | True | | | Template specification. |
diff --git a/docs/en/latest/plugins/ai-request-rewrite.md
b/docs/en/latest/plugins/ai-request-rewrite.md
index df518598d..cdeddb008 100644
--- a/docs/en/latest/plugins/ai-request-rewrite.md
+++ b/docs/en/latest/plugins/ai-request-rewrite.md
@@ -42,6 +42,7 @@ The `ai-request-rewrite` Plugin processes client requests by
forwarding them to
| Name | Type | Required | Default | Valid values | Description |
| --- | --- | --- | --- | --- | --- |
+| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request
body size in bytes buffered into memory. Requests with a larger body are
rejected. |
| `prompt` | string | True | | | The prompt to send to the LLM service for
rewriting the client request. |
| `provider` | string | True | | [openai, deepseek, azure-openai, aimlapi,
gemini, vertex-ai, anthropic, openrouter, openai-compatible] | LLM service
provider. When set to `aimlapi`, the Plugin uses the OpenAI-compatible driver
and proxies the request to `https://api.aimlapi.com/v1/chat/completions`. When
set to `openai-compatible`, the Plugin proxies requests to the custom endpoint
configured in `override`. When set to `azure-openai`, the Plugin also proxies
requests to the custom endpo [...]
| `auth` | object | True | | | Authentication configurations. |
diff --git a/docs/en/latest/plugins/authz-keycloak.md
b/docs/en/latest/plugins/authz-keycloak.md
index 194bdc770..a5a83f22f 100644
--- a/docs/en/latest/plugins/authz-keycloak.md
+++ b/docs/en/latest/plugins/authz-keycloak.md
@@ -45,6 +45,7 @@ While the Plugin was developed for Keycloak, it could
theoretically be used with
| Name | Type | Required |
Default | Valid values
| Description
|
|----------------------------------------------|---------------|----------|-----------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| discovery | string | False |
|
https://host.domain/realms/foo/.well-known/uma2-configuration | URL to
[discovery
document](https://www.keycloak.org/docs/latest/authorization_services/index.html)
of Keycloak Authorization Services.
|
| token_endpoint | string | False |
|
https://host.domain/realms/foo/protocol/openid-connect/token | An
OAuth2-compliant token endpoint that supports the
`urn:ietf:params:oauth:grant-type:uma-ticket` grant type. If provided,
overrides the value from discovery.
|
| resource_registration_endpoint | string | False |
|
https://host.domain/realms/foo/authz/protection/resource_set | A
UMA-compliant resource registration endpoint. If provided, overrides the value
from discovery.
|
diff --git a/docs/en/latest/plugins/aws-lambda.md
b/docs/en/latest/plugins/aws-lambda.md
index 3538ec6b7..6e094eb95 100644
--- a/docs/en/latest/plugins/aws-lambda.md
+++ b/docs/en/latest/plugins/aws-lambda.md
@@ -44,6 +44,7 @@ The Plugin supports authentication and authorization with AWS
via IAM user crede
| Name | Type | Required | Default | Valid
values | Description
|
|------------------------------|---------|----------|---------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| function_uri | string | True | |
| AWS Lambda function URL or Amazon API Gateway endpoint that triggers the
Lambda function.
|
| authorization | object | False | |
| Credentials used in authentication and authorization on AWS to invoke
Lambda function.
|
| authorization.apikey | string | False | |
| API key for the REST API Gateway when API key is selected as the security
mechanism.
|
diff --git a/docs/en/latest/plugins/azure-functions.md
b/docs/en/latest/plugins/azure-functions.md
index 07663eb07..142b8deb9 100644
--- a/docs/en/latest/plugins/azure-functions.md
+++ b/docs/en/latest/plugins/azure-functions.md
@@ -37,6 +37,7 @@ When enabled, the Plugin terminates the ongoing request to
the configured URI an
| Name | Type | Required | Default | Valid values |
Description
|
|------------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| function_uri | string | True | | | Azure
FunctionS endpoint which triggers the serverless function. For example,
`http://test-apisix.azurewebsites.net/api/HttpTrigger`. |
| authorization | object | False | | |
Authorization credentials to access Azure Functions.
|
| authorization.apikey | string | False | | |
Generated API key to authorize requests.
|
diff --git a/docs/en/latest/plugins/body-transformer.md
b/docs/en/latest/plugins/body-transformer.md
index 3fde732ab..195e37a3f 100644
--- a/docs/en/latest/plugins/body-transformer.md
+++ b/docs/en/latest/plugins/body-transformer.md
@@ -40,6 +40,8 @@ The `body-transformer` Plugin performs template-based
transformations to transfo
| Name | Type | Required | Default | Valid values | Description
|
| ------------- | ------- | -------- | ------- | ------------ |
------------------------------------------ |
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
+| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response
body size in bytes buffered into memory. Larger responses are truncated. |
| `request` | object | False | | | Request body transformation
configuration. |
| `request.input_format` | string | False | |
[`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] | Request body original
media type. If unspecified, the value would be determined by the `Content-Type`
header to apply the corresponding decoder. The `xml` option corresponds to
`text/xml` media type. The `json` option corresponds to `application/json`
media type. The `encoded` option corresponds to
`application/x-www-form-urlencoded` media type. The `args` option correspond
[...]
| `request.template` | string | True | | | Request body
transformation template. The template uses
[lua-resty-template](https://github.com/bungle/lua-resty-template) syntax. See
the [template
syntax](https://github.com/bungle/lua-resty-template#template-syntax) for more
details. You can also use auxiliary functions `_escape_json()` and
`_escape_xml()` to escape special characters such as double quotes, `_body` to
access request body, and `_ctx` to access context variables. |
diff --git a/docs/en/latest/plugins/cas-auth.md
b/docs/en/latest/plugins/cas-auth.md
index 3d64e226c..d5bb98fa1 100644
--- a/docs/en/latest/plugins/cas-auth.md
+++ b/docs/en/latest/plugins/cas-auth.md
@@ -37,6 +37,7 @@ to do authentication, from the SP (service provider)
perspective.
| Name | Type | Required | Default | Description |
| ----------- | ----------- | ----------- | ----------- | ----------- |
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| `idp_uri` | string | True | | URI of IdP. |
| `cas_callback_uri` | string | True | | redirect uri used to
callback the SP from IdP after login or logout. |
| `logout_uri` | string | True | | logout uri to trigger
logout. |
diff --git a/docs/en/latest/plugins/degraphql.md
b/docs/en/latest/plugins/degraphql.md
index bd0d55ca8..984265539 100644
--- a/docs/en/latest/plugins/degraphql.md
+++ b/docs/en/latest/plugins/degraphql.md
@@ -39,6 +39,7 @@ The `degraphql` Plugin supports communicating with upstream
GraphQL services ove
| Name | Type | Required | Description
|
| ---------------- | ------------ | -------- |
--------------------------------------------------------------------------------------------------
|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| `query` | string | True | The GraphQL query sent to the
Upstream. |
| `operation_name` | string | False | The name of the operation, only
required if multiple operations are present in the query. |
| `variables` | array[string]| False | The names of variables used in
the GraphQL query, extracted from the request body or query string. |
diff --git a/docs/en/latest/plugins/grpc-transcode.md
b/docs/en/latest/plugins/grpc-transcode.md
index 425fc9520..4b756d363 100644
--- a/docs/en/latest/plugins/grpc-transcode.md
+++ b/docs/en/latest/plugins/grpc-transcode.md
@@ -42,6 +42,7 @@ With this Plugin enabled, APISIX accepts an HTTP request from
the client, transc
| Name | Type
| Required | Default
| Description
|
|----------------------|--------------------------------------------------------|----------|-----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response
body size in bytes buffered into memory for transcoding. Larger responses are
truncated. |
| proto_id | string/integer
| True |
| ID of the proto resource, which contains the protocol buffer
definitions.
|
| service | string
| True |
| Name of the gRPC service.
|
| method | string
| True |
| Method name of the gRPC service.
|
diff --git a/docs/en/latest/plugins/grpc-web.md
b/docs/en/latest/plugins/grpc-web.md
index ff2e4c1a5..f1c55663c 100644
--- a/docs/en/latest/plugins/grpc-web.md
+++ b/docs/en/latest/plugins/grpc-web.md
@@ -42,6 +42,7 @@ The `grpc-web` Plugin translates gRPC-Web requests into
native gRPC calls and fo
| Name | Type | Required | Default
| Description
|
|----------------------|---------|----------|-------------------------------------------|---------------------------------------------------------------------------------------------------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| cors_allow_headers | string | False |
`content-type,x-grpc-web,x-user-agent` | Comma-separated list of request
headers allowed for cross-origin requests. |
## Request Handling
diff --git a/docs/en/latest/plugins/http-dubbo.md
b/docs/en/latest/plugins/http-dubbo.md
index 9cced1bce..3441536a3 100755
--- a/docs/en/latest/plugins/http-dubbo.md
+++ b/docs/en/latest/plugins/http-dubbo.md
@@ -38,6 +38,7 @@ Dubbo 2.x, the serialization type of the upstream service
must be fastjson).
| Name | Type | Required | Default | Valid values |
Description
[...]
|--------------------------|---------|----------|---------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[...]
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| service_name | string | True | | |
Dubbo service name
[...]
| service_version | string | False | 0.0.0 | |
Dubbo service version
[...]
| method | string | True | | |
Dubbo service method name
[...]
diff --git a/docs/en/latest/plugins/oas-validator.md
b/docs/en/latest/plugins/oas-validator.md
index a96fe2c3e..d4292508e 100644
--- a/docs/en/latest/plugins/oas-validator.md
+++ b/docs/en/latest/plugins/oas-validator.md
@@ -43,6 +43,7 @@ The OpenAPI spec can be provided as an inline JSON string or
fetched from a remo
| Name | Type | Required | Default | Valid values | Description |
|------|------|----------|---------|--------------|-------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| spec | string | No* | | | Inline OpenAPI 3.x specification in JSON format.
Required if `spec_url` is not set. |
| spec_url | string | No* | | `^https?://` | URL to fetch the OpenAPI
specification from. Required if `spec` is not set. |
| spec_url_request_headers | object | No | | | Custom HTTP request headers
sent when fetching `spec_url`. Useful for authenticated specification
endpoints. |
diff --git a/docs/en/latest/plugins/openfunction.md
b/docs/en/latest/plugins/openfunction.md
index 12340ee1d..fa338f076 100644
--- a/docs/en/latest/plugins/openfunction.md
+++ b/docs/en/latest/plugins/openfunction.md
@@ -37,6 +37,7 @@ This Plugin can be configured on a Route and requests will be
sent to the config
| Name | Type | Required | Default | Valid values |
Description
|
| --------------------------- | ------- | -------- | ------- | ------------ |
----------------------------------------------------------------------------------------------------------
|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| function_uri | string | True | | |
function uri. For example, `https://localhost:30858/default/function-sample`.
|
| ssl_verify | boolean | False | true | |
When set to `true` verifies the SSL certificate.
|
| authorization | object | False | | |
Authorization credentials to access functions of OpenFunction.
|
diff --git a/docs/en/latest/plugins/openwhisk.md
b/docs/en/latest/plugins/openwhisk.md
index c3371f261..78f624eb7 100644
--- a/docs/en/latest/plugins/openwhisk.md
+++ b/docs/en/latest/plugins/openwhisk.md
@@ -37,6 +37,7 @@ This Plugin can be configured on a Route and requests will be
send to the config
| Name | Type | Required | Default | Valid values |
Description
|
| ----------------- | ------- | -------- | ------- | ------------ |
----------------------------------------------------------------------------------------------------------
|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| api_host | string | True | | | OpenWhisk
API host address. For example, `https://localhost:3233`.
|
| ssl_verify | boolean | False | true | | When set
to `true` verifies the SSL certificate.
|
| service_token | string | True | | | OpenWhisk
service token. The format is `xxx:xxx` and it is passed through basic auth when
calling the API. |
diff --git a/docs/en/latest/plugins/proxy-cache.md
b/docs/en/latest/plugins/proxy-cache.md
index c44e07a3b..e43142a31 100644
--- a/docs/en/latest/plugins/proxy-cache.md
+++ b/docs/en/latest/plugins/proxy-cache.md
@@ -43,6 +43,7 @@ Responses can be conditionally cached based on request HTTP
methods, response st
| Name | Type | Required | Default |
Valid values | Description
|
|--------------------|----------------|----------|---------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response
body size in bytes buffered into memory by the `memory` cache strategy. Larger
responses are streamed through without being cached. |
| cache_strategy | string | False | disk |
["disk","memory"] | Caching strategy. Cache on disk or in memory.
|
| cache_zone | string | False | disk_cache_one |
| Cache zone used with the caching strategy. The value
should match one of the cache zones defined in the [configuration
files](#static-configurations) and should correspond to the caching strategy.
For example, when using the in-memory caching strategy, you should use an
in-memory cache zone. |
| cache_key | array[string] | False | ["$host", "$request_uri"] |
| Key to use for caching. Support [NGINX
variables](https://nginx.org/en/docs/varindex.html) and constant strings in
values. Variables should be prefixed with a `$` sign. |
diff --git a/docs/en/latest/plugins/request-validation.md
b/docs/en/latest/plugins/request-validation.md
index 83ec7eaf3..e136339e5 100644
--- a/docs/en/latest/plugins/request-validation.md
+++ b/docs/en/latest/plugins/request-validation.md
@@ -43,6 +43,7 @@ See [JSON schema
specification](https://json-schema.org/specification) to learn
| Name | Type | Required | Default | Valid values | Description
|
|---------------|---------|----------|---------|---------------|---------------------------------------------------|
+| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body
size in bytes buffered into memory. Requests with a larger body are rejected. |
| header_schema | object | False | | | Schema for
the request header data. |
| body_schema | object | False | | | Schema for
the request body data. |
| rejected_code | integer | False | 400 | [200,...,599] | Status code
to return when rejecting requests. |
diff --git a/docs/en/latest/plugins/response-rewrite.md
b/docs/en/latest/plugins/response-rewrite.md
index ec3bd8a7a..74bc7beb7 100644
--- a/docs/en/latest/plugins/response-rewrite.md
+++ b/docs/en/latest/plugins/response-rewrite.md
@@ -51,6 +51,7 @@ You can also use the [redirect](./redirect.md) Plugin to set
up redirects.
| Name | Type | Required | Default | Valid values
| Description
|
|-----------------|---------|----------|---------|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response
body size in bytes buffered into memory when `filters` are applied. Larger
responses are truncated. |
| status_code | integer | False | | [200, 598]
| New HTTP status code in the response. If unset, falls back to the original
status code.
|
| body | string | False | |
| New response body. The `Content-Length` header would also be reset. Should
not be configured with `filters`.
|
| body_base64 | boolean | False | false |
| If true, decode the response body configured in `body` before sending to
client, which is useful for image and protobuf decoding. Note that this
configuration cannot be used to decode Upstream response.
|
diff --git a/t/plugin/request-validation.t b/t/plugin/request-validation.t
index 790ccdea7..6d525a822 100644
--- a/t/plugin/request-validation.t
+++ b/t/plugin/request-validation.t
@@ -1866,3 +1866,56 @@ POST /echo
duplicated content-type
--- error_log
duplicated Content-Type header
+
+
+
+=== TEST 57: add route with a small max_req_body_size
+--- 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-validation": {
+ "max_req_body_size": 10,
+ "body_schema": {
+ "type": "object",
+ "required": ["required_payload"],
+ "properties": {
+ "required_payload": {"type": "string"}
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/echo"
+ }]])
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 58: body larger than max_req_body_size is rejected
+--- more_headers
+Content-Type: application/json
+--- request
+POST /echo
+{"required_payload": "this body is well over ten bytes"}
+--- error_code: 400
+--- error_log
+request size
diff --git a/t/plugin/response-rewrite.t b/t/plugin/response-rewrite.t
index fda43bedc..bf9645ce8 100644
--- a/t/plugin/response-rewrite.t
+++ b/t/plugin/response-rewrite.t
@@ -733,3 +733,51 @@ GET /hello
GET /t
--- response_body
passed
+
+
+
+=== TEST 28: filters with a small max_resp_body_size
+--- 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": {
+ "response-rewrite": {
+ "max_resp_body_size": 5,
+ "filters": [
+ {
+ "regex": "hello",
+ "scope": "once",
+ "replace": "HELLO"
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uris": ["/hello"]
+ }]]
+ )
+
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 29: response body is truncated to max_resp_body_size before filtering
+--- request
+GET /hello
+--- response_body chomp
+HELLO