This is an automated email from the ASF dual-hosted git repository.
bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new ef9e3ce967 Fix request buffering with post_copy_size=0 (#12702)
ef9e3ce967 is described below
commit ef9e3ce967a13c1dcbf43c2d03e865a9eec8b97d
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Dec 2 11:11:11 2025 -0600
Fix request buffering with post_copy_size=0 (#12702)
When proxy.config.http.request_buffer_enabled is set to 1 with
proxy.config.http.post_copy_size set to 0, POST requests were failing.
The fix disables request buffering when post_copy_size is 0, treating it
as an indication to not buffer POST data.
Fixes: #6900
---
src/proxy/http/HttpConfig.cc | 10 +-
src/proxy/http/HttpTransact.cc | 2 +-
.../pluginTest/test_hooks/body_buffer.test.py | 119 +------------------
.../test_hooks/replay/body_buffer.replay.yaml | 123 ++++++++++++++++++++
.../replay/zero_post_copy_size.replay.yaml | 127 +++++++++++++++++++++
5 files changed, 264 insertions(+), 117 deletions(-)
diff --git a/src/proxy/http/HttpConfig.cc b/src/proxy/http/HttpConfig.cc
index adead9915d..baac34ddf9 100644
--- a/src/proxy/http/HttpConfig.cc
+++ b/src/proxy/http/HttpConfig.cc
@@ -1345,9 +1345,13 @@ HttpConfig::reconfigure()
params->redirection_host_no_port =
INT_TO_BOOL(m_master.redirection_host_no_port);
params->oride.number_of_redirections =
m_master.oride.number_of_redirections;
params->post_copy_size = m_master.post_copy_size;
- params->redirect_actions_string =
ats_strdup(m_master.redirect_actions_string);
- params->redirect_actions_map =
parse_redirect_actions(params->redirect_actions_string,
params->redirect_actions_self_action);
- params->http_host_sni_policy = m_master.http_host_sni_policy;
+ if (params->oride.request_buffer_enabled && params->post_copy_size == 0) {
+ Warning("proxy.config.http.request_buffer_enabled is set but
proxy.config.http.post_copy_size is 0; request buffering "
+ "will be disabled");
+ }
+ params->redirect_actions_string =
ats_strdup(m_master.redirect_actions_string);
+ params->redirect_actions_map =
parse_redirect_actions(params->redirect_actions_string,
params->redirect_actions_self_action);
+ params->http_host_sni_policy = m_master.http_host_sni_policy;
params->scheme_proto_mismatch_policy = m_master.scheme_proto_mismatch_policy;
params->oride.ssl_client_sni_policy =
ats_strdup(m_master.oride.ssl_client_sni_policy);
diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc
index 9ab7302eaa..a911d82c0c 100644
--- a/src/proxy/http/HttpTransact.cc
+++ b/src/proxy/http/HttpTransact.cc
@@ -1573,7 +1573,7 @@ HttpTransact::HandleRequest(State *s)
}
}
}
- if (s->txn_conf->request_buffer_enabled &&
+ if (s->txn_conf->request_buffer_enabled &&
s->http_config_param->post_copy_size > 0 &&
s->state_machine->get_ua_txn()->has_request_body(s->hdr_info.request_content_length,
s->client_info.transfer_encoding == TransferEncoding_t::CHUNKED)) {
TRANSACT_RETURN(StateMachineAction_t::WAIT_FOR_FULL_BODY, nullptr);
diff --git a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
index 07e1934341..16c966dd3d 100644
--- a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
+++ b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py
@@ -1,5 +1,5 @@
'''
-Verify HTTP body buffering.
+Verify HTTP body buffering with request_buffer.so plugin.
'''
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -17,118 +17,11 @@ Verify HTTP body buffering.
# See the License for the specific language governing permissions and
# limitations under the License.
-Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
-
-
-def int_to_hex_string(int_value):
- '''
- Convert the given int value to a hex string with no '0x' prefix.
-
- >>> int_to_hex_string(0)
- '0'
- >>> int_to_hex_string(1)
- '1'
- >>> int_to_hex_string(10)
- 'a'
- >>> int_to_hex_string(16)
- '10'
- >>> int_to_hex_string(17)
- 'f1'
- '''
- if not isinstance(int_value, int):
- raise ValueError("Input should be an int type.")
- return hex(int_value).split('x')[1]
-
-
-class BodyBufferTest:
+Test.Summary = 'Verify HTTP body buffering with request_buffer.so plugin.'
- def __init__(cls, description):
- Test.Summary = description
- cls._origin_max_connections = 3
- cls.setupOriginServer()
- cls.setupTS()
-
- def setupOriginServer(self):
- self._server = Test.MakeOriginServer("server")
- self.content_length_request_body = "content-length request"
- self.content_length_size = len(self.content_length_request_body)
- request_header = {
- "headers":
- "POST /contentlength HTTP/1.1\r\n"
- "Host: www.example.com\r\n"
- f"Content-Length: {self.content_length_size}\r\n\r\n",
- "timestamp": "1469733493.993",
- "body": self.content_length_request_body
- }
- content_length_response_body = "content-length response"
- content_length_response_size = len(content_length_response_body)
- response_header = {
- "headers":
- "HTTP/1.1 200 OK\r\n"
- "Server: microserver\r\n"
- f"Content-Length: {content_length_response_size}\r\n\r\n"
- "Connection: close\r\n\r\n",
- "timestamp": "1469733493.993",
- "body": content_length_response_body
- }
- self._server.addResponse("sessionlog.json", request_header,
response_header)
-
- self.chunked_request_body = "chunked request"
- hex_size = int_to_hex_string(len(self.chunked_request_body))
- self.encoded_chunked_request =
f"{hex_size}\r\n{self.chunked_request_body}\r\n0\r\n\r\n"
- self.encoded_chunked_size = len(self.content_length_request_body)
- request_header2 = {
- "headers":
- "POST /chunked HTTP/1.1\r\n"
- "Transfer-Encoding: chunked\r\n"
- "Host: www.example.com\r\n"
- "Connection: keep-alive\r\n\r\n",
- "timestamp": "1469733493.993",
- "body": self.encoded_chunked_request
- }
- self.chunked_response_body = "chunked response"
- hex_size = int_to_hex_string(len(self.chunked_response_body))
- self.encoded_chunked_response =
f"{hex_size}\r\n{self.chunked_response_body}\r\n0\r\n\r\n"
- response_header2 = {
- "headers": "HTTP/1.1 200 OK\r\n"
- "Transfer-Encoding: chunked\r\n"
- "Server: microserver\r\n"
- "Connection: close\r\n\r\n",
- "timestamp": "1469733493.993",
- "body": self.encoded_chunked_response
- }
- self._server.addResponse("sessionlog.json", request_header2,
response_header2)
-
- def setupTS(self):
- self._ts = Test.MakeATSProcess("ts", select_ports=False)
- self._ts.Disk.remap_config.AddLine(f'map /
http://127.0.0.1:{self._server.Variables.Port}')
- Test.PrepareInstalledPlugin('request_buffer.so', self._ts)
- self._ts.Disk.records_config.update(
- {
- 'proxy.config.diags.debug.enabled': 1,
- 'proxy.config.diags.debug.tags': 'request_buffer',
- 'proxy.config.http.server_ports': str(self._ts.Variables.port)
+ f" {self._ts.Variables.uds_path}",
- })
-
- self._ts.Disk.traffic_out.Content = Testers.ContainsExpression(
- rf"request_buffer_plugin gets the request body with
length\[{self.content_length_size}\]",
- "Verify that the plugin parsed the content-length request body
data.")
- self._ts.Disk.traffic_out.Content += Testers.ContainsExpression(
- rf"request_buffer_plugin gets the request body with
length\[{self.encoded_chunked_size}\]",
- "Verify that the plugin parsed the chunked request body.")
-
- def run(self):
- tr = Test.AddTestRun()
- # Send both a Content-Length request and a chunked-encoded request.
- tr.MakeCurlCommand(
- f'-v http://127.0.0.1:{self._ts.Variables.port}/contentlength -d
"{self.content_length_request_body}" --next '
- f'-v http://127.0.0.1:{self._ts.Variables.port}/chunked -H
"Transfer-Encoding: chunked" -d "{self.chunked_request_body}"',
- ts=self._ts)
- tr.Processes.Default.ReturnCode = 0
- tr.Processes.Default.StartBefore(self._server)
- tr.Processes.Default.StartBefore(Test.Processes.ts)
- tr.Processes.Default.Streams.stderr = "200.gold"
+Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
+Test.ATSReplayTest(replay_file="replay/body_buffer.replay.yaml")
-bodyBufferTest = BodyBufferTest("Test request body buffering.")
-bodyBufferTest.run()
+# Test for issue #6900: post_copy_size=0 with request_buffer_enabled should
not cause 403.
+Test.ATSReplayTest(replay_file="replay/zero_post_copy_size.replay.yaml")
diff --git
a/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml
b/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml
new file mode 100644
index 0000000000..a629e2da98
--- /dev/null
+++ b/tests/gold_tests/pluginTest/test_hooks/replay/body_buffer.replay.yaml
@@ -0,0 +1,123 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Test request body buffering with request_buffer.so plugin.
+#
+meta:
+ version: "1.0"
+
+autest:
+ description: 'Verify request body buffering with request_buffer.so plugin'
+
+ server:
+ name: 'body-buffer-server'
+
+ client:
+ name: 'body-buffer-client'
+
+ ats:
+ name: 'ts-body-buffer'
+ process_config:
+ enable_cache: false
+
+ plugin_config:
+ - request_buffer.so
+
+ records_config:
+ proxy.config.diags.debug.enabled: 1
+ proxy.config.diags.debug.tags: 'request_buffer'
+
+ remap_config:
+ - from: /
+ to: http://127.0.0.1:{SERVER_HTTP_PORT}/
+
+ log_validation:
+ traffic_out:
+ contains:
+ - expression: "request_buffer_plugin gets the request body with
length\\[22\\]"
+ description: "Verify that the plugin parsed the content-length
request body data"
+ - expression: "request_buffer_plugin gets the request body with
length\\[25\\]"
+ description: "Verify that the plugin parsed the chunked request
body data"
+
+sessions:
+ - transactions:
+ # Content-Length POST request
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /contentlength
+ headers:
+ fields:
+ - [Host, www.example.com]
+ - [Content-Length, 22]
+ - [uuid, content-length-post]
+ content:
+ data: "content-length request"
+
+ proxy-request:
+ method: "POST"
+ url: /contentlength
+ content:
+ verify: { value: "content-length request", as: equal }
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Content-Length, 23]
+ content:
+ data: "content-length response"
+
+ proxy-response:
+ status: 200
+ content:
+ verify: { value: "content-length response", as: equal }
+
+ # Chunked POST request
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /chunked
+ headers:
+ fields:
+ - [Host, www.example.com]
+ - [Transfer-Encoding, chunked]
+ - [uuid, chunked-post]
+ content:
+ data: "chunked request"
+
+ proxy-request:
+ method: "POST"
+ url: /chunked
+ content:
+ verify: { value: "f\r\nchunked request\r\n0\r\n\r\n", as: equal }
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Transfer-Encoding, chunked]
+ content:
+ data: "chunked response"
+
+ proxy-response:
+ status: 200
+ content:
+ verify: { value: "10\r\nchunked response\r\n0\r\n\r\n", as: equal }
+
diff --git
a/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml
b/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml
new file mode 100644
index 0000000000..c34af71d87
--- /dev/null
+++
b/tests/gold_tests/pluginTest/test_hooks/replay/zero_post_copy_size.replay.yaml
@@ -0,0 +1,127 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Test for issue #6900: Verify that post_copy_size=0 with
request_buffer_enabled=1
+# does not cause 403 errors. When post_copy_size is 0, request buffering
should be
+# disabled and POST requests should proceed normally.
+#
+meta:
+ version: "1.0"
+
+autest:
+ description: 'Verify post_copy_size=0 with request_buffer_enabled does not
cause 403 (issue #6900)'
+
+ server:
+ name: 'zero-copy-server'
+
+ client:
+ name: 'zero-copy-client'
+
+ ats:
+ name: 'ts-zero-copy'
+ process_config:
+ enable_cache: false
+
+ plugin_config:
+ - request_buffer.so
+
+ records_config:
+ proxy.config.diags.debug.enabled: 1
+ proxy.config.diags.debug.tags: 'http|request_buffer'
+ # The key configuration: post_copy_size=0 with request_buffer_enabled=1
+ # This should NOT cause 403 errors (issue #6900 fix)
+ proxy.config.http.post_copy_size: 0
+ proxy.config.http.request_buffer_enabled: 1
+
+ remap_config:
+ - from: /
+ to: http://127.0.0.1:{SERVER_HTTP_PORT}/
+
+ log_validation:
+ diags_log:
+ contains:
+ - expression: "request_buffer_enabled is set but
proxy.config.http.post_copy_size is 0"
+ description: "Verify warning is logged at startup for
misconfiguration"
+
+sessions:
+ - transactions:
+ # POST with a body - should return 200, not 403
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /post_small
+ headers:
+ fields:
+ - [Host, example.com]
+ - [Content-Length, 5]
+ - [uuid, zero-copy-small]
+ content:
+ data: "small"
+
+ proxy-request:
+ method: "POST"
+ url: /post_small
+ content:
+ verify: { value: "small", as: equal }
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Content-Length, 2]
+ content:
+ data: OK
+
+ proxy-response:
+ status: 200
+ content:
+ verify: { value: "OK", as: equal }
+
+ # Same with a large body.
+ - client-request:
+ method: "POST"
+ version: "1.1"
+ url: /post_large
+ headers:
+ fields:
+ - [Host, example.com]
+ - [Content-Length, 10000]
+ - [uuid, zero-copy-large]
+
+ proxy-request:
+ method: "POST"
+ url: /post_large
+ headers:
+ fields:
+ - [Content-Length, {value: 10000, as: equal}]
+ content:
+ size: 10000
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Content-Length, 2]
+ content:
+ data: OK
+
+ proxy-response:
+ status: 200
+ content:
+ verify: { value: "OK", as: equal }