Skip to content

Commit 7501d11

Browse files
committed
escalate: POST body escalation fix
This adds a test for POST body failover via the escalate plugin. This also fixes the tunnel logic so the body can be sent, especially for small POST bodies.
1 parent a57a824 commit 7501d11

6 files changed

Lines changed: 114 additions & 20 deletions

File tree

doc/admin-guide/plugins/escalate.en.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ when the origin server in the remap rule returns a 401,
5959
option overrides the default behavior and enables escalation for non-GET
6060
requests in addition to GET.
6161

62+
.. note::
63+
64+
For POST body buffering to work with escalation,
65+
:ts:cv:`proxy.config.http.post_copy_size` must be set large enough to buffer
66+
the expected POST body sizes (e.g., 2048 bytes or larger). This enables
67+
Traffic Server to buffer POST bodies before sending to the origin, so they
68+
can be replayed if escalation is needed.
69+
6270
Installation
6371
------------
6472

plugins/escalate/escalate.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ EscalateResponse(TSCont cont, TSEvent event, void *edata)
209209

210210
// Now update the Redirect URL, if set
211211
if (url_str) {
212+
Dbg(dbg_ctl, "Calling TSHttpTxnRedirectUrlSet for status %d with URL: %.*s", status, url_len, url_str);
212213
TSHttpTxnRedirectUrlSet(txn, url_str, url_len); // Transfers ownership
213214

214215
// Add our x-escalate-redirect header marker if it doesn't already exist and the option is enabled.
@@ -320,6 +321,11 @@ TSRemapDoRemap(void *instance, TSHttpTxn txn, TSRemapRequestInfo * /* rri */)
320321
{
321322
EscalationState *es = static_cast<EscalationState *>(instance);
322323

324+
// Note: For POST body buffering to work with escalation,
325+
// proxy.config.http.post_copy_size should be set large enough to buffer the
326+
// expected POST body sizes. This enables ATS to buffer POST bodies before
327+
// sending to the origin so they can be replayed if escalation is needed.
328+
323329
TSHttpTxnHookAdd(txn, TS_HTTP_READ_RESPONSE_HDR_HOOK, es->cont);
324330
TSHttpTxnHookAdd(txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, es->cont);
325331
return TSREMAP_NO_REMAP;

src/proxy/http/HttpTunnel.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,12 @@ HttpTunnel::producer_run(HttpTunnelProducer *p)
10281028
return;
10291029
}
10301030
} else {
1031-
body_bytes_copied += sm->postbuf_copy_partial_data(body_bytes_to_copy);
1032-
body_bytes_to_copy = 0;
1031+
// For POST redirect buffering, we need to copy data that's already in the buffer.
1032+
// When POST data is pre-buffered (common for small POSTs), producer_n (ntodo) is 0,
1033+
// so body_bytes_to_copy calculated earlier would be 0. Instead, copy what's available.
1034+
int64_t bytes_to_copy_now = p->buffer_start->read_avail();
1035+
body_bytes_copied += sm->postbuf_copy_partial_data(bytes_to_copy_now);
1036+
body_bytes_to_copy = 0;
10331037
}
10341038
} // end of added logic for partial POST
10351039

tests/gold_tests/pluginTest/escalate/escalate.test.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ def _setup_servers(self, tr: 'TestRun') -> None:
205205
'uuid: GET_failed', "Verify the origin server received the failed GET request.")
206206
self._server_origin.Streams.All += Testers.ContainsExpression(
207207
'uuid: POST_success', "Verify the origin server received the successful POST request.")
208+
self._server_origin.Streams.All += Testers.ContainsExpression(
209+
'uuid: POST_fail_escalated', "Verify the origin server received the POST request that will be escalated.")
208210
self._server_origin.Streams.All += Testers.ContainsExpression(
209211
'uuid: HEAD_fail_escalated', "Verify the origin server received the HEAD request that will be escalated.")
210212

@@ -217,6 +219,9 @@ def _setup_servers(self, tr: 'TestRun') -> None:
217219
'uuid: GET_failed', "Verify the failover server received the failed GET request.")
218220
self._server_failover.Streams.All += Testers.ContainsExpression(
219221
'uuid: GET_down_origin', "Verify the failover server received the down origin GET request.")
222+
# With --escalate-non-get-methods, the POST request should now be escalated
223+
self._server_failover.Streams.All += Testers.ContainsExpression(
224+
'uuid: POST_fail_escalated', "Verify the failover server received the POST that is now escalated.")
220225
# With --escalate-non-get-methods, the HEAD request should now be escalated
221226
self._server_failover.Streams.All += Testers.ContainsExpression(
222227
'uuid: HEAD_fail_escalated', "Verify the failover server received the HEAD that is now escalated.")
@@ -231,11 +236,12 @@ def _setup_ts(self, tr: 'TestRun') -> None:
231236
self._ts.Disk.records_config.update(
232237
{
233238
'proxy.config.diags.debug.enabled': 1,
234-
'proxy.config.diags.debug.tags': 'http|escalate',
239+
'proxy.config.diags.debug.tags': 'http|escalate|http_redirect',
235240
'proxy.config.dns.nameservers': f'127.0.0.1:{self._dns.Variables.Port}',
236241
'proxy.config.dns.resolv_conf': 'NULL',
237242
'proxy.config.http.redirect.actions': 'self:follow',
238243
'proxy.config.http.number_of_redirections': 4,
244+
'proxy.config.http.post_copy_size': 400000,
239245
})
240246

241247
# Set up a dead port for the down origin scenario
@@ -266,6 +272,8 @@ def _setup_client(self, tr: 'TestRun') -> None:
266272
client.Streams.All += Testers.ContainsExpression('x-response: third', 'Verify third GET response received (escalated).')
267273
client.Streams.All += Testers.ContainsExpression('x-response: fourth', 'Verify fourth GET response received (escalated).')
268274
client.Streams.All += Testers.ContainsExpression('x-response: post_success', 'Verify successful POST response received.')
275+
client.Streams.All += Testers.ContainsExpression(
276+
'x-response: post_fail_escalated', 'Verify escalated POST response received.')
269277
client.Streams.All += Testers.ContainsExpression(
270278
'x-response: head_fail_escalated', 'Verify escalated HEAD response received.')
271279

tests/gold_tests/pluginTest/escalate/escalate_failover.replay.yaml

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,50 @@ sessions:
125125
content:
126126
size: 320000
127127

128+
# POST request response for escalated requests (with --escalate-non-get-methods)
129+
- client-request:
130+
method: "POST"
131+
version: "1.1"
132+
url: /api/post/data
133+
headers:
134+
fields:
135+
- [ Host, origin.server.com ]
136+
- [ Content-Type, "application/json" ]
137+
- [ Content-Length, 320000 ]
138+
- [ X-Request, post_fail_escalated ]
139+
- [ uuid, POST_fail_escalated ]
140+
141+
proxy-request:
142+
method: "POST"
143+
headers:
144+
fields:
145+
- [ X-Request, { value: post_fail_escalated, as: equal } ]
146+
- [ Content-Length, { value: 320000, as: equal } ]
147+
148+
server-response:
149+
status: 200
150+
reason: OK
151+
headers:
152+
fields:
153+
- [ Content-Length, 320000 ]
154+
- [ X-Response, post_fail_escalated ]
155+
128156
# HEAD request response for escalated requests (with --escalate-non-get-methods)
129157
- client-request:
130158
method: "HEAD"
131159
version: "1.1"
132-
url: /api/head/data
160+
url: /path/head_fail
133161
headers:
134162
fields:
135163
- [ Host, origin.server.com ]
136-
- [ X-Request, head_fail_escalated ]
164+
- [ X-Request, head_fail ]
137165
- [ uuid, HEAD_fail_escalated ]
138166

139167
proxy-request:
140168
method: "HEAD"
141169
headers:
142170
fields:
143-
- [ X-Request, { value: head_fail_escalated, as: equal } ]
171+
- [ X-Request, { value: head_fail, as: equal } ]
144172

145173
server-response:
146174
status: 200

tests/gold_tests/pluginTest/escalate/escalate_non_get_methods.replay.yaml

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ meta:
1919

2020
sessions:
2121
- transactions:
22-
# Original GET transactions from escalate_original.replay.yaml
22+
# Non-escalated GET request.
2323
- client-request:
2424
method: "GET"
2525
version: "1.1"
@@ -51,6 +51,7 @@ sessions:
5151
fields:
5252
- [ X-Response, { value: first, as: equal } ]
5353

54+
# Non-escalated GET request with a chunked response.
5455
- client-request:
5556
method: "GET"
5657
version: "1.1"
@@ -84,6 +85,7 @@ sessions:
8485
fields:
8586
- [ X-Response, { value: second, as: equal } ]
8687

88+
# GET request that will fail and be escalated
8789
- client-request:
8890
method: "GET"
8991
version: "1.1"
@@ -107,13 +109,16 @@ sessions:
107109
headers:
108110
fields:
109111
- [ Content-Length, 0 ]
112+
- [ X-Response, third ]
110113

114+
# With escalation, the failover responds with 200
111115
proxy-response:
112-
# The failover server should reply with a 200 OK (GET requests are escalated with --escalate-non-get-methods).
113116
status: 200
114117
headers:
115118
fields:
116119
- [ X-Response, { value: third, as: equal } ]
120+
content:
121+
size: 320000
117122

118123
# This will not make it to the origin server since the Host is set to a
119124
# non-responsive server. But the failover server should reply with a 200 OK.
@@ -134,6 +139,7 @@ sessions:
134139
fields:
135140
- [ X-Request, { value: fourth, as: equal } ]
136141

142+
# This server-response is just for replay file validity - origin server won't receive this transaction
137143
server-response:
138144
status: 200
139145
reason: OK
@@ -147,34 +153,32 @@ sessions:
147153
fields:
148154
- [ X-Response, { value: fourth, as: equal } ]
149155

150-
# POST request with sizable body that gets proxied normally.
156+
# POST request that succeeds without escalation
151157
- client-request:
152158
method: "POST"
153159
version: "1.1"
154-
url: /api/upload/data
160+
url: /api/post/success
155161
headers:
156162
fields:
157163
- [ Host, origin.server.com ]
158164
- [ Content-Type, "application/json" ]
159-
- [ Content-Length, 32 ]
165+
- [ Content-Length, 320000 ]
160166
- [ X-Request, post_success ]
161167
- [ uuid, POST_success ]
162-
content:
163-
encoding: plain
164-
size: 32
165168

166169
proxy-request:
167170
method: "POST"
168171
headers:
169172
fields:
170173
- [ X-Request, { value: post_success, as: equal } ]
174+
- [ Content-Length, { value: 320000, as: equal } ]
171175

172176
server-response:
173177
status: 200
174178
reason: OK
175179
headers:
176180
fields:
177-
- [ Content-Length, 32 ]
181+
- [ Content-Length, 0 ]
178182
- [ X-Response, post_success ]
179183

180184
proxy-response:
@@ -183,30 +187,66 @@ sessions:
183187
fields:
184188
- [ X-Response, { value: post_success, as: equal } ]
185189

186-
# A HEAD request that will be escalated with --escalate-non-get-methods
190+
# POST request with body that will be escalated with --escalate-non-get-methods
191+
- client-request:
192+
method: "POST"
193+
version: "1.1"
194+
url: /api/post/data
195+
headers:
196+
fields:
197+
- [ Host, origin.server.com ]
198+
- [ Content-Type, "application/json" ]
199+
- [ Content-Length, 320000 ]
200+
- [ X-Request, post_fail_escalated ]
201+
- [ uuid, POST_fail_escalated ]
202+
203+
proxy-request:
204+
method: "POST"
205+
headers:
206+
fields:
207+
- [ X-Request, { value: post_fail_escalated, as: equal } ]
208+
- [ Content-Length, { value: 320000, as: equal } ]
209+
210+
server-response:
211+
status: 502
212+
reason: Bad Gateway
213+
headers:
214+
fields:
215+
- [ Content-Length, 0 ]
216+
- [ X-Response, post_fail_escalated ]
217+
218+
# With --escalate-non-get-methods, POST request is escalated to failover.
219+
proxy-response:
220+
status: 200
221+
headers:
222+
fields:
223+
- [ X-Response, { value: post_fail_escalated, as: equal } ]
224+
225+
# HEAD request that will be escalated with --escalate-non-get-methods
187226
- client-request:
188227
method: "HEAD"
189228
version: "1.1"
190-
url: /api/head/data
229+
url: /path/head_fail
191230
headers:
192231
fields:
193232
- [ Host, origin.server.com ]
194-
- [ X-Request, head_fail_escalated ]
233+
- [ Content-Length, 0 ]
234+
- [ X-Request, head_fail ]
195235
- [ uuid, HEAD_fail_escalated ]
196236

197237
proxy-request:
198238
method: "HEAD"
199239
headers:
200240
fields:
201-
- [ X-Request, { value: head_fail_escalated, as: equal } ]
241+
- [ X-Request, { value: head_fail, as: equal } ]
202242

203243
server-response:
204244
status: 502
205245
reason: Bad Gateway
206246
headers:
207247
fields:
208248
- [ Content-Length, 0 ]
209-
- [ X-Response, head_fail_escalated ]
249+
- [ X-Response, head_fail ]
210250

211251
# With --escalate-non-get-methods, HEAD request is escalated to failover.
212252
proxy-response:

0 commit comments

Comments
 (0)