comparison body_discard.t @ 1961:fe6f22da53ec

Tests: tests for usage of discarded body. The client_max_body_size limit should be ignored when the request body is already discarded. In HTTP/1.x, this is done by checking the r->discard_body flag when the body is being discarded, and because r->headers_in.content_length_n is 0 when it's already discarded. This, however, does not happen with HTTP/2 and HTTP/3, and therefore "error_page 413" does not work without relaxing the limit. Further, with proxy_pass, r->headers_in.content_length_n is used to determine length of the request body, and therefore is not correct if discarding of the request body isn't yet complete. While discarding the request body, r->headers_in.content_length_n contains the rest of the body to discard (or, in case of chunked request body, the rest of the current chunk to discard). Similarly, the $content_length variable uses r->headers_in.content_length if available, and also incorrect. The $content_length variable is used when proxying with fastcgi_pass, grpc_pass, and uwsgi_pass (scgi_pass uses the value calculated based on the actual request body buffers, and therefore works correctly).
author Maxim Dounin <mdounin@mdounin.ru>
date Sat, 27 Apr 2024 18:55:50 +0300
parents
children
comparison
equal deleted inserted replaced
1960:e44ee916b959 1961:fe6f22da53ec
1 #!/usr/bin/perl
2
3 # (C) Maxim Dounin
4
5 # Tests for discarding request body.
6
7 ###############################################################################
8
9 use warnings;
10 use strict;
11
12 use Test::More;
13 use Socket qw/ CRLF /;
14
15 BEGIN { use FindBin; chdir($FindBin::Bin); }
16
17 use lib 'lib';
18 use Test::Nginx;
19
20 ###############################################################################
21
22 select STDERR; $| = 1;
23 select STDOUT; $| = 1;
24
25 my $t = Test::Nginx->new()
26 ->has(qw/http proxy rewrite addition memcached/);
27
28 plan(skip_all => 'not yet') unless $t->has_version('1.27.0');
29
30 $t->plan(33)->write_file_expand('nginx.conf', <<'EOF');
31
32 %%TEST_GLOBALS%%
33
34 daemon off;
35
36 events {
37 }
38
39 http {
40 %%TEST_GLOBALS_HTTP%%
41
42 server {
43 listen 127.0.0.1:8080;
44 server_name localhost;
45
46 lingering_timeout 1s;
47 add_header X-Body body:$content_length:$request_body:;
48
49 client_max_body_size 1k;
50
51 error_page 400 /proxy/error400;
52
53 location / {
54 error_page 413 /error413;
55 proxy_pass http://127.0.0.1:8082;
56 }
57
58 location /error413 {
59 return 200 "custom error 413";
60 }
61
62 location /add {
63 return 200 "main response";
64 add_before_body /add/before;
65 addition_types *;
66 client_max_body_size 1m;
67 }
68
69 location /add/before {
70 proxy_pass http://127.0.0.1:8081;
71 }
72
73 location /memcached {
74 client_max_body_size 1m;
75 error_page 502 /memcached/error502;
76 memcached_pass 127.0.0.1:8083;
77 set $memcached_key $request_uri;
78 }
79
80 location /memcached/error502 {
81 proxy_pass http://127.0.0.1:8081;
82 }
83
84 location /proxy {
85 client_max_body_size 1;
86 error_page 413 /proxy/error413;
87 error_page 400 /proxy/error400;
88 error_page 502 /proxy/error502;
89 proxy_pass http://127.0.0.1:8083;
90 }
91
92 location /proxy/error413 {
93 proxy_pass http://127.0.0.1:8081;
94 }
95
96 location /proxy/error400 {
97 proxy_pass http://127.0.0.1:8081;
98 }
99
100 location /proxy/error502 {
101 proxy_pass http://127.0.0.1:8081;
102 }
103
104 location /unbuf {
105 client_max_body_size 1m;
106 error_page 502 /unbuf/error502;
107 proxy_pass http://127.0.0.1:8083;
108 proxy_request_buffering off;
109 proxy_http_version 1.1;
110 }
111
112 location /unbuf/error502 {
113 client_max_body_size 1m;
114 proxy_pass http://127.0.0.1:8081;
115 }
116
117 location /length {
118 client_max_body_size 1;
119 error_page 413 /length/error413;
120 error_page 502 /length/error502;
121 proxy_pass http://127.0.0.1:8083;
122 }
123
124 location /length/error413 {
125 return 200 "frontend body:$content_length:$request_body:";
126 }
127
128 location /length/error502 {
129 return 200 "frontend body:$content_length:$request_body:";
130 }
131 }
132
133 server {
134 listen 127.0.0.1:8081;
135 server_name localhost;
136
137 location / {
138 proxy_pass http://127.0.0.1:8082;
139 proxy_set_header X-Body body:$content_length:$request_body:;
140 }
141 }
142
143 server {
144 listen 127.0.0.1:8082;
145 server_name localhost;
146
147 return 200 "backend $http_x_body";
148 }
149
150 server {
151 listen 127.0.0.1:8083;
152 server_name localhost;
153
154 return 444;
155 }
156 }
157
158 EOF
159
160 $t->run();
161
162 ###############################################################################
163
164 # error_page 413 should work without redefining client_max_body_size
165
166 like(http(
167 'POST / HTTP/1.0' . CRLF .
168 'Content-Length: 10000' . CRLF . CRLF .
169 '0123456789'
170 ), qr/ 413 .*custom error 413/s, 'custom error 413');
171
172 # subrequest after discarding body
173
174 like(http(
175 'GET /add HTTP/1.0' . CRLF . CRLF
176 ), qr/backend body:::.*main response/s, 'add');
177
178 like(http(
179 'POST /add HTTP/1.0' . CRLF .
180 'Content-Length: 10' . CRLF . CRLF .
181 '0123456789'
182 ), qr/backend body:::.*main response/s, 'add small');
183
184 like(http(
185 'POST /add HTTP/1.0' . CRLF .
186 'Content-Length: 10000' . CRLF . CRLF .
187 '0123456789'
188 ), qr/backend body:::.*main response/s, 'add long');
189
190 like(http(
191 'POST /add HTTP/1.1' . CRLF .
192 'Host: localhost' . CRLF .
193 'Connection: close' . CRLF .
194 'Transfer-Encoding: chunked' . CRLF . CRLF .
195 'a' . CRLF .
196 '0123456789' . CRLF .
197 '0' . CRLF . CRLF
198 ), qr/backend body:::.*main response/s, 'add chunked');
199
200 like(http(
201 'POST /add HTTP/1.1' . CRLF .
202 'Host: localhost' . CRLF .
203 'Connection: close' . CRLF .
204 'Transfer-Encoding: chunked' . CRLF . CRLF .
205 '1' . CRLF .
206 'X' . CRLF .
207 '9' . CRLF .
208 '123456789' . CRLF .
209 '0' . CRLF . CRLF
210 ), qr/backend body:::.*main response/s, 'add chunked multi');
211
212 like(http(
213 'POST /add HTTP/1.1' . CRLF .
214 'Host: localhost' . CRLF .
215 'Connection: close' . CRLF .
216 'Transfer-Encoding: chunked' . CRLF . CRLF .
217 'ffff' . CRLF .
218 '0123456789'
219 ), qr/backend body:::.*main response/s, 'add chunked long');
220
221 # error_page 502 with proxy_pass after discarding body
222
223 like(http(
224 'GET /memcached HTTP/1.0' . CRLF . CRLF
225 ), qr/ 502 .*backend body:::/s, 'memcached');
226
227 like(http(
228 'GET /memcached HTTP/1.0' . CRLF .
229 'Content-Length: 10' . CRLF . CRLF .
230 '0123456789'
231 ), qr/ 502 .*backend body:::/s, 'memcached small');
232
233 like(http(
234 'GET /memcached HTTP/1.0' . CRLF .
235 'Content-Length: 10000' . CRLF . CRLF .
236 '0123456789'
237 ), qr/ 502 .*backend body:::/s, 'memcached long');
238
239 like(http(
240 'GET /memcached HTTP/1.1' . CRLF .
241 'Host: localhost' . CRLF .
242 'Connection: close' . CRLF .
243 'Transfer-Encoding: chunked' . CRLF . CRLF .
244 'a' . CRLF .
245 '0123456789' . CRLF .
246 '0' . CRLF . CRLF
247 ), qr/ 502 .*backend body:::/s, 'memcached chunked');
248
249 like(http(
250 'GET /memcached HTTP/1.1' . CRLF .
251 'Host: localhost' . CRLF .
252 'Connection: close' . CRLF .
253 'Transfer-Encoding: chunked' . CRLF . CRLF .
254 '1' . CRLF .
255 'X' . CRLF .
256 '9' . CRLF .
257 '123456789' . CRLF .
258 '0' . CRLF . CRLF
259 ), qr/ 502 .*backend body:::/s, 'memcached chunked multi');
260
261 like(http(
262 'GET /memcached HTTP/1.1' . CRLF .
263 'Host: localhost' . CRLF .
264 'Connection: close' . CRLF .
265 'Transfer-Encoding: chunked' . CRLF . CRLF .
266 'ffff' . CRLF .
267 '0123456789'
268 ), qr/ 502 .*backend body:::/s, 'memcached chunked long');
269
270 # error_page 413 with proxy_pass
271
272 like(http(
273 'GET /proxy HTTP/1.0' . CRLF . CRLF
274 ), qr/ 502 .*backend body:::/s, 'proxy');
275
276 like(http(
277 'POST /proxy HTTP/1.0' . CRLF .
278 'Content-Length: 10' . CRLF . CRLF .
279 '0123456789'
280 ), qr/ 413 .*backend body:::/s, 'proxy small');
281
282 like(http(
283 'POST /proxy HTTP/1.0' . CRLF .
284 'Content-Length: 10000' . CRLF . CRLF .
285 '0123456789'
286 ), qr/ 413 .*backend body:::/s, 'proxy long');
287
288 like(http(
289 'POST /proxy HTTP/1.1' . CRLF .
290 'Host: localhost' . CRLF .
291 'Connection: close' . CRLF .
292 'Transfer-Encoding: chunked' . CRLF . CRLF .
293 'a' . CRLF .
294 '0123456789' . CRLF .
295 '0' . CRLF . CRLF
296 ), qr/ 413 .*backend body:::/s, 'proxy chunked');
297
298 like(http(
299 'POST /proxy HTTP/1.1' . CRLF .
300 'Host: localhost' . CRLF .
301 'Connection: close' . CRLF .
302 'Transfer-Encoding: chunked' . CRLF . CRLF .
303 '1' . CRLF .
304 'X' . CRLF .
305 '9' . CRLF .
306 '123456789' . CRLF .
307 '0' . CRLF . CRLF
308 ), qr/ 413 .*backend body:::/s, 'proxy chunked multi');
309
310 like(http(
311 'POST /proxy HTTP/1.1' . CRLF .
312 'Host: localhost' . CRLF .
313 'Connection: close' . CRLF .
314 'Transfer-Encoding: chunked' . CRLF . CRLF .
315 'ffff' . CRLF .
316 '0123456789'
317 ), qr/ 413 .*backend body:::/s, 'proxy chunked long');
318
319 # error_page 400 with proxy_pass
320
321 # note that "chunked and length" test triggers 400 during parsing
322 # request headers, and therefore needs error_page at server level
323
324 like(http(
325 'POST /proxy HTTP/1.1' . CRLF .
326 'Host: localhost' . CRLF .
327 'Connection: close' . CRLF .
328 'Transfer-Encoding: chunked' . CRLF . CRLF .
329 '1' . CRLF .
330 'X' . CRLF .
331 'X' . CRLF
332 ), qr/ 400 .*backend body:::/s, 'proxy chunked bad');
333
334 like(http(
335 'POST /proxy HTTP/1.1' . CRLF .
336 'Host: localhost' . CRLF .
337 'Connection: close' . CRLF .
338 'Content-Length: 10' . CRLF .
339 'Transfer-Encoding: chunked' . CRLF . CRLF .
340 '0' . CRLF . CRLF
341 ), qr/ 400 .*backend body:::/s, 'proxy chunked and length');
342
343 # error_page 502 after proxy with request buffering disabled
344
345 like(http(
346 'GET /unbuf HTTP/1.0' . CRLF . CRLF
347 ), qr/ 502 .*backend body:::/s, 'unbuf proxy');
348
349 like(http(
350 'POST /unbuf HTTP/1.0' . CRLF .
351 'Content-Length: 10' . CRLF . CRLF .
352 '0',
353 sleep => 0.1,
354 body =>
355 '123456789'
356 ), qr/ 502 .*backend body:::/s, 'unbuf proxy small');
357
358 like(http(
359 'POST /unbuf HTTP/1.0' . CRLF .
360 'Content-Length: 10000' . CRLF . CRLF .
361 '0123456789'
362 ), qr/ 502 .*backend body:::/s, 'unbuf proxy long');
363
364 like(http(
365 'POST /unbuf HTTP/1.1' . CRLF .
366 'Host: localhost' . CRLF .
367 'Connection: close' . CRLF .
368 'Transfer-Encoding: chunked' . CRLF . CRLF,
369 sleep => 0.1,
370 body =>
371 'a' . CRLF .
372 '0123456789' . CRLF .
373 '0' . CRLF . CRLF
374 ), qr/ 502 .*backend body:::/s, 'unbuf proxy chunked');
375
376 like(http(
377 'POST /unbuf HTTP/1.1' . CRLF .
378 'Host: localhost' . CRLF .
379 'Connection: close' . CRLF .
380 'Transfer-Encoding: chunked' . CRLF . CRLF .
381 '1' . CRLF .
382 'X' . CRLF,
383 sleep => 0.1,
384 body =>
385 '9' . CRLF .
386 '123456789' . CRLF .
387 '0' . CRLF . CRLF
388 ), qr/ 502 .*backend body:::/s, 'unbuf proxy chunked multi');
389
390 like(http(
391 'POST /unbuf HTTP/1.1' . CRLF .
392 'Host: localhost' . CRLF .
393 'Connection: close' . CRLF .
394 'Transfer-Encoding: chunked' . CRLF . CRLF .
395 'ffff' . CRLF .
396 '0123456789'
397 ), qr/ 502 .*backend body:::/s, 'unbuf proxy chunked long');
398
399 # error_page 413 and $content_length
400 # (used in fastcgi_pass, grpc_pass, uwsgi_pass)
401
402 like(http(
403 'GET /length HTTP/1.0' . CRLF . CRLF
404 ), qr/ 502 .*frontend body:::/s, '$content_length');
405
406 like(http(
407 'POST /length HTTP/1.0' . CRLF .
408 'Content-Length: 10' . CRLF . CRLF .
409 '0123456789'
410 ), qr/ 413 .*frontend body:::/s, '$content_length small');
411
412 like(http(
413 'POST /length HTTP/1.0' . CRLF .
414 'Content-Length: 10000' . CRLF . CRLF .
415 '0123456789'
416 ), qr/ 413 .*frontend body:::/s, '$content_length long');
417
418 like(http(
419 'POST /length HTTP/1.1' . CRLF .
420 'Host: localhost' . CRLF .
421 'Connection: close' . CRLF .
422 'Transfer-Encoding: chunked' . CRLF . CRLF .
423 'a' . CRLF .
424 '0123456789' . CRLF .
425 '0' . CRLF . CRLF
426 ), qr/ 413 .*frontend body:::/s, '$content_length chunked');
427
428 like(http(
429 'POST /length HTTP/1.1' . CRLF .
430 'Host: localhost' . CRLF .
431 'Connection: close' . CRLF .
432 'Transfer-Encoding: chunked' . CRLF . CRLF .
433 '1' . CRLF .
434 'X' . CRLF .
435 '9' . CRLF .
436 '123456789' . CRLF .
437 '0' . CRLF . CRLF
438 ), qr/ 413 .*frontend body:::/s, '$content_length chunked multi');
439
440 like(http(
441 'POST /length HTTP/1.1' . CRLF .
442 'Host: localhost' . CRLF .
443 'Connection: close' . CRLF .
444 'Transfer-Encoding: chunked' . CRLF . CRLF .
445 'ffff' . CRLF .
446 '0123456789'
447 ), qr/ 413 .*frontend body:::/s, '$content_length chunked long');
448
449 ###############################################################################