Skip to content

Commit ef9e3ce

Browse files
authored
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
1 parent b38147f commit ef9e3ce

File tree

5 files changed

+264
-117
lines changed

5 files changed

+264
-117
lines changed

src/proxy/http/HttpConfig.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,9 +1345,13 @@ HttpConfig::reconfigure()
13451345
params->redirection_host_no_port = INT_TO_BOOL(m_master.redirection_host_no_port);
13461346
params->oride.number_of_redirections = m_master.oride.number_of_redirections;
13471347
params->post_copy_size = m_master.post_copy_size;
1348-
params->redirect_actions_string = ats_strdup(m_master.redirect_actions_string);
1349-
params->redirect_actions_map = parse_redirect_actions(params->redirect_actions_string, params->redirect_actions_self_action);
1350-
params->http_host_sni_policy = m_master.http_host_sni_policy;
1348+
if (params->oride.request_buffer_enabled && params->post_copy_size == 0) {
1349+
Warning("proxy.config.http.request_buffer_enabled is set but proxy.config.http.post_copy_size is 0; request buffering "
1350+
"will be disabled");
1351+
}
1352+
params->redirect_actions_string = ats_strdup(m_master.redirect_actions_string);
1353+
params->redirect_actions_map = parse_redirect_actions(params->redirect_actions_string, params->redirect_actions_self_action);
1354+
params->http_host_sni_policy = m_master.http_host_sni_policy;
13511355
params->scheme_proto_mismatch_policy = m_master.scheme_proto_mismatch_policy;
13521356

13531357
params->oride.ssl_client_sni_policy = ats_strdup(m_master.oride.ssl_client_sni_policy);

src/proxy/http/HttpTransact.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ HttpTransact::HandleRequest(State *s)
15731573
}
15741574
}
15751575
}
1576-
if (s->txn_conf->request_buffer_enabled &&
1576+
if (s->txn_conf->request_buffer_enabled && s->http_config_param->post_copy_size > 0 &&
15771577
s->state_machine->get_ua_txn()->has_request_body(s->hdr_info.request_content_length,
15781578
s->client_info.transfer_encoding == TransferEncoding_t::CHUNKED)) {
15791579
TRANSACT_RETURN(StateMachineAction_t::WAIT_FOR_FULL_BODY, nullptr);
Lines changed: 6 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'''
2-
Verify HTTP body buffering.
2+
Verify HTTP body buffering with request_buffer.so plugin.
33
'''
44
# Licensed to the Apache Software Foundation (ASF) under one
55
# or more contributor license agreements. See the NOTICE file
@@ -17,118 +17,11 @@
1717
# See the License for the specific language governing permissions and
1818
# limitations under the License.
1919

20-
Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
21-
22-
23-
def int_to_hex_string(int_value):
24-
'''
25-
Convert the given int value to a hex string with no '0x' prefix.
26-
27-
>>> int_to_hex_string(0)
28-
'0'
29-
>>> int_to_hex_string(1)
30-
'1'
31-
>>> int_to_hex_string(10)
32-
'a'
33-
>>> int_to_hex_string(16)
34-
'10'
35-
>>> int_to_hex_string(17)
36-
'f1'
37-
'''
38-
if not isinstance(int_value, int):
39-
raise ValueError("Input should be an int type.")
40-
return hex(int_value).split('x')[1]
41-
42-
43-
class BodyBufferTest:
20+
Test.Summary = 'Verify HTTP body buffering with request_buffer.so plugin.'
4421

45-
def __init__(cls, description):
46-
Test.Summary = description
47-
cls._origin_max_connections = 3
48-
cls.setupOriginServer()
49-
cls.setupTS()
50-
51-
def setupOriginServer(self):
52-
self._server = Test.MakeOriginServer("server")
53-
self.content_length_request_body = "content-length request"
54-
self.content_length_size = len(self.content_length_request_body)
55-
request_header = {
56-
"headers":
57-
"POST /contentlength HTTP/1.1\r\n"
58-
"Host: www.example.com\r\n"
59-
f"Content-Length: {self.content_length_size}\r\n\r\n",
60-
"timestamp": "1469733493.993",
61-
"body": self.content_length_request_body
62-
}
63-
content_length_response_body = "content-length response"
64-
content_length_response_size = len(content_length_response_body)
65-
response_header = {
66-
"headers":
67-
"HTTP/1.1 200 OK\r\n"
68-
"Server: microserver\r\n"
69-
f"Content-Length: {content_length_response_size}\r\n\r\n"
70-
"Connection: close\r\n\r\n",
71-
"timestamp": "1469733493.993",
72-
"body": content_length_response_body
73-
}
74-
self._server.addResponse("sessionlog.json", request_header, response_header)
75-
76-
self.chunked_request_body = "chunked request"
77-
hex_size = int_to_hex_string(len(self.chunked_request_body))
78-
self.encoded_chunked_request = f"{hex_size}\r\n{self.chunked_request_body}\r\n0\r\n\r\n"
79-
self.encoded_chunked_size = len(self.content_length_request_body)
80-
request_header2 = {
81-
"headers":
82-
"POST /chunked HTTP/1.1\r\n"
83-
"Transfer-Encoding: chunked\r\n"
84-
"Host: www.example.com\r\n"
85-
"Connection: keep-alive\r\n\r\n",
86-
"timestamp": "1469733493.993",
87-
"body": self.encoded_chunked_request
88-
}
89-
self.chunked_response_body = "chunked response"
90-
hex_size = int_to_hex_string(len(self.chunked_response_body))
91-
self.encoded_chunked_response = f"{hex_size}\r\n{self.chunked_response_body}\r\n0\r\n\r\n"
92-
response_header2 = {
93-
"headers": "HTTP/1.1 200 OK\r\n"
94-
"Transfer-Encoding: chunked\r\n"
95-
"Server: microserver\r\n"
96-
"Connection: close\r\n\r\n",
97-
"timestamp": "1469733493.993",
98-
"body": self.encoded_chunked_response
99-
}
100-
self._server.addResponse("sessionlog.json", request_header2, response_header2)
101-
102-
def setupTS(self):
103-
self._ts = Test.MakeATSProcess("ts", select_ports=False)
104-
self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.Port}')
105-
Test.PrepareInstalledPlugin('request_buffer.so', self._ts)
106-
self._ts.Disk.records_config.update(
107-
{
108-
'proxy.config.diags.debug.enabled': 1,
109-
'proxy.config.diags.debug.tags': 'request_buffer',
110-
'proxy.config.http.server_ports': str(self._ts.Variables.port) + f" {self._ts.Variables.uds_path}",
111-
})
112-
113-
self._ts.Disk.traffic_out.Content = Testers.ContainsExpression(
114-
rf"request_buffer_plugin gets the request body with length\[{self.content_length_size}\]",
115-
"Verify that the plugin parsed the content-length request body data.")
116-
self._ts.Disk.traffic_out.Content += Testers.ContainsExpression(
117-
rf"request_buffer_plugin gets the request body with length\[{self.encoded_chunked_size}\]",
118-
"Verify that the plugin parsed the chunked request body.")
119-
120-
def run(self):
121-
tr = Test.AddTestRun()
122-
# Send both a Content-Length request and a chunked-encoded request.
123-
tr.MakeCurlCommand(
124-
f'-v http://127.0.0.1:{self._ts.Variables.port}/contentlength -d "{self.content_length_request_body}" --next '
125-
f'-v http://127.0.0.1:{self._ts.Variables.port}/chunked -H "Transfer-Encoding: chunked" -d "{self.chunked_request_body}"',
126-
ts=self._ts)
127-
tr.Processes.Default.ReturnCode = 0
128-
tr.Processes.Default.StartBefore(self._server)
129-
tr.Processes.Default.StartBefore(Test.Processes.ts)
130-
tr.Processes.Default.Streams.stderr = "200.gold"
22+
Test.SkipUnless(Condition.PluginExists('request_buffer.so'))
13123

24+
Test.ATSReplayTest(replay_file="replay/body_buffer.replay.yaml")
13225

133-
bodyBufferTest = BodyBufferTest("Test request body buffering.")
134-
bodyBufferTest.run()
26+
# Test for issue #6900: post_copy_size=0 with request_buffer_enabled should not cause 403.
27+
Test.ATSReplayTest(replay_file="replay/zero_post_copy_size.replay.yaml")
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
#
18+
# Test request body buffering with request_buffer.so plugin.
19+
#
20+
meta:
21+
version: "1.0"
22+
23+
autest:
24+
description: 'Verify request body buffering with request_buffer.so plugin'
25+
26+
server:
27+
name: 'body-buffer-server'
28+
29+
client:
30+
name: 'body-buffer-client'
31+
32+
ats:
33+
name: 'ts-body-buffer'
34+
process_config:
35+
enable_cache: false
36+
37+
plugin_config:
38+
- request_buffer.so
39+
40+
records_config:
41+
proxy.config.diags.debug.enabled: 1
42+
proxy.config.diags.debug.tags: 'request_buffer'
43+
44+
remap_config:
45+
- from: /
46+
to: http://127.0.0.1:{SERVER_HTTP_PORT}/
47+
48+
log_validation:
49+
traffic_out:
50+
contains:
51+
- expression: "request_buffer_plugin gets the request body with length\\[22\\]"
52+
description: "Verify that the plugin parsed the content-length request body data"
53+
- expression: "request_buffer_plugin gets the request body with length\\[25\\]"
54+
description: "Verify that the plugin parsed the chunked request body data"
55+
56+
sessions:
57+
- transactions:
58+
# Content-Length POST request
59+
- client-request:
60+
method: "POST"
61+
version: "1.1"
62+
url: /contentlength
63+
headers:
64+
fields:
65+
- [Host, www.example.com]
66+
- [Content-Length, 22]
67+
- [uuid, content-length-post]
68+
content:
69+
data: "content-length request"
70+
71+
proxy-request:
72+
method: "POST"
73+
url: /contentlength
74+
content:
75+
verify: { value: "content-length request", as: equal }
76+
77+
server-response:
78+
status: 200
79+
reason: OK
80+
headers:
81+
fields:
82+
- [Content-Length, 23]
83+
content:
84+
data: "content-length response"
85+
86+
proxy-response:
87+
status: 200
88+
content:
89+
verify: { value: "content-length response", as: equal }
90+
91+
# Chunked POST request
92+
- client-request:
93+
method: "POST"
94+
version: "1.1"
95+
url: /chunked
96+
headers:
97+
fields:
98+
- [Host, www.example.com]
99+
- [Transfer-Encoding, chunked]
100+
- [uuid, chunked-post]
101+
content:
102+
data: "chunked request"
103+
104+
proxy-request:
105+
method: "POST"
106+
url: /chunked
107+
content:
108+
verify: { value: "f\r\nchunked request\r\n0\r\n\r\n", as: equal }
109+
110+
server-response:
111+
status: 200
112+
reason: OK
113+
headers:
114+
fields:
115+
- [Transfer-Encoding, chunked]
116+
content:
117+
data: "chunked response"
118+
119+
proxy-response:
120+
status: 200
121+
content:
122+
verify: { value: "10\r\nchunked response\r\n0\r\n\r\n", as: equal }
123+

0 commit comments

Comments
 (0)