Mercurial > hg > nginx-tests
comparison h3_request_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 with HTTP/3. | |
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 use Test::Nginx::HTTP3; | |
20 | |
21 ############################################################################### | |
22 | |
23 select STDERR; $| = 1; | |
24 select STDOUT; $| = 1; | |
25 | |
26 my $t = Test::Nginx->new() | |
27 ->has(qw/http http_v3 proxy rewrite addition memcached cryptx/) | |
28 ->has_daemon('openssl'); | |
29 | |
30 plan(skip_all => 'not yet') unless $t->has_version('1.27.0'); | |
31 | |
32 $t->plan(37)->write_file_expand('nginx.conf', <<'EOF'); | |
33 | |
34 %%TEST_GLOBALS%% | |
35 | |
36 daemon off; | |
37 | |
38 events { | |
39 } | |
40 | |
41 http { | |
42 %%TEST_GLOBALS_HTTP%% | |
43 | |
44 ssl_certificate localhost.crt; | |
45 ssl_certificate_key localhost.key; | |
46 | |
47 server { | |
48 listen 127.0.0.1:%%PORT_8980_UDP%% quic; | |
49 server_name localhost; | |
50 | |
51 lingering_timeout 1s; | |
52 add_header X-Body body:$content_length:$request_body:; | |
53 | |
54 client_max_body_size 1k; | |
55 | |
56 location / { | |
57 error_page 413 /error413; | |
58 proxy_pass http://127.0.0.1:8082; | |
59 } | |
60 | |
61 location /error413 { | |
62 return 200 "custom error 413"; | |
63 } | |
64 | |
65 location /add { | |
66 return 200 "main response"; | |
67 add_before_body /add/before; | |
68 addition_types *; | |
69 client_max_body_size 1m; | |
70 } | |
71 | |
72 location /add/before { | |
73 proxy_pass http://127.0.0.1:8081; | |
74 } | |
75 | |
76 location /memcached { | |
77 client_max_body_size 1m; | |
78 error_page 502 /memcached/error502; | |
79 memcached_pass 127.0.0.1:8083; | |
80 set $memcached_key $request_uri; | |
81 } | |
82 | |
83 location /memcached/error502 { | |
84 proxy_pass http://127.0.0.1:8081; | |
85 } | |
86 | |
87 location /proxy { | |
88 client_max_body_size 3; | |
89 error_page 413 /proxy/error413; | |
90 error_page 400 /proxy/error400; | |
91 error_page 502 /proxy/error502; | |
92 proxy_pass http://127.0.0.1:8083; | |
93 } | |
94 | |
95 location /proxy/error413 { | |
96 proxy_pass http://127.0.0.1:8081; | |
97 } | |
98 | |
99 location /proxy/error400 { | |
100 proxy_pass http://127.0.0.1:8081; | |
101 } | |
102 | |
103 location /proxy/error502 { | |
104 proxy_pass http://127.0.0.1:8081; | |
105 } | |
106 | |
107 location /unbuf { | |
108 client_max_body_size 1m; | |
109 error_page 502 /unbuf/error502; | |
110 proxy_pass http://127.0.0.1:8083; | |
111 proxy_request_buffering off; | |
112 proxy_http_version 1.1; | |
113 } | |
114 | |
115 location /unbuf/error502 { | |
116 client_max_body_size 1m; | |
117 proxy_pass http://127.0.0.1:8081; | |
118 } | |
119 | |
120 location /unbuf2 { | |
121 client_max_body_size 1m; | |
122 error_page 400 /unbuf2/error400; | |
123 proxy_pass http://127.0.0.1:8081; | |
124 proxy_request_buffering off; | |
125 proxy_http_version 1.1; | |
126 } | |
127 | |
128 location /unbuf2/error400 { | |
129 client_max_body_size 1m; | |
130 proxy_pass http://127.0.0.1:8081; | |
131 } | |
132 | |
133 location /length { | |
134 client_max_body_size 1; | |
135 error_page 413 /length/error413; | |
136 error_page 502 /length/error502; | |
137 proxy_pass http://127.0.0.1:8083; | |
138 } | |
139 | |
140 location /length/error413 { | |
141 return 200 "frontend body:$content_length:$request_body:"; | |
142 } | |
143 | |
144 location /length/error502 { | |
145 return 200 "frontend body:$content_length:$request_body:"; | |
146 } | |
147 } | |
148 | |
149 server { | |
150 listen 127.0.0.1:8081; | |
151 server_name localhost; | |
152 | |
153 location / { | |
154 proxy_pass http://127.0.0.1:8082; | |
155 proxy_set_header X-Body body:$content_length:$request_body:; | |
156 } | |
157 } | |
158 | |
159 server { | |
160 listen 127.0.0.1:8082; | |
161 server_name localhost; | |
162 | |
163 return 200 "backend $http_x_body"; | |
164 } | |
165 | |
166 server { | |
167 listen 127.0.0.1:8083; | |
168 server_name localhost; | |
169 | |
170 return 444; | |
171 } | |
172 } | |
173 | |
174 EOF | |
175 | |
176 $t->write_file('openssl.conf', <<EOF); | |
177 [ req ] | |
178 default_bits = 2048 | |
179 encrypt_key = no | |
180 distinguished_name = req_distinguished_name | |
181 [ req_distinguished_name ] | |
182 EOF | |
183 | |
184 my $d = $t->testdir(); | |
185 | |
186 foreach my $name ('localhost') { | |
187 system('openssl req -x509 -new ' | |
188 . "-config $d/openssl.conf -subj /CN=$name/ " | |
189 . "-out $d/$name.crt -keyout $d/$name.key " | |
190 . ">>$d/openssl.out 2>&1") == 0 | |
191 or die "Can't create certificate for $name: $!\n"; | |
192 } | |
193 | |
194 $t->run(); | |
195 | |
196 ############################################################################### | |
197 | |
198 # error_page 413 should work without redefining client_max_body_size | |
199 | |
200 like(http3_get_body('/', '0123456789' x 128), | |
201 qr/status: 413.*custom error 413/s, 'custom error 413'); | |
202 | |
203 # subrequest after discarding body | |
204 | |
205 like(http3_get('/add'), | |
206 qr/backend body:::.*main response/s, 'add'); | |
207 like(http3_get_body('/add', '0123456789'), | |
208 qr/backend body:::.*main response/s, 'add small'); | |
209 like(http3_get_body_incomplete('/add', 10000, '0123456789'), | |
210 qr/backend body:::.*main response/s, 'add long'); | |
211 like(http3_get_body_nolen('/add', '0123456789'), | |
212 qr/backend body:::.*main response/s, 'add nolen'); | |
213 like(http3_get_body_nolen('/add', '0', '123456789'), | |
214 qr/backend body:::.*main response/s, 'add nolen multi'); | |
215 like(http3_get_body_incomplete_nolen('/add', 10000, '0123456789'), | |
216 qr/backend body:::.*main response/s, 'add chunked long'); | |
217 | |
218 # error_page 502 with proxy_pass after discarding body | |
219 | |
220 like(http3_get('/memcached'), | |
221 qr/backend body:::/s, 'memcached'); | |
222 like(http3_get_body('/memcached', '0123456789'), | |
223 qr/status: 502.*backend body:::/s, 'memcached small'); | |
224 like(http3_get_body_incomplete('/memcached', 10000, '0123456789'), | |
225 qr/status: 502.*backend body:::/s, 'memcached long'); | |
226 like(http3_get_body_nolen('/memcached', '0123456789'), | |
227 qr/status: 502.*backend body:::/s, 'memcached nolen'); | |
228 like(http3_get_body_nolen('/memcached', '0', '123456789'), | |
229 qr/status: 502.*backend body:::/s, 'memcached nolen multi'); | |
230 like(http3_get_body_incomplete_nolen('/memcached', 10000, '0123456789'), | |
231 qr/status: 502.*backend body:::/s, 'memcached nolen long'); | |
232 | |
233 # error_page 413 with proxy_pass | |
234 | |
235 like(http3_get('/proxy'), | |
236 qr/status: 502.*backend body:::/s, 'proxy'); | |
237 like(http3_get_body('/proxy', '0123456789'), | |
238 qr/status: 413.*backend body:::/s, 'proxy small'); | |
239 like(http3_get_body_incomplete('/proxy', 10000, '0123456789'), | |
240 qr/status: 413.*backend body:::/s, 'proxy long'); | |
241 like(http3_get_body_nolen('/proxy', '0123456789'), | |
242 qr/status: 413.*backend body:::/s, 'proxy nolen'); | |
243 like(http3_get_body_nolen('/proxy', '0', '123456789'), | |
244 qr/status: 413.*backend body:::/s, 'proxy nolen multi'); | |
245 like(http3_get_body_incomplete_nolen('/proxy', '0123456789'), | |
246 qr/status: 413.*backend body:::/s, 'proxy nolen long'); | |
247 | |
248 # error_page 400 with proxy_pass | |
249 | |
250 like(http3_get_body_custom('/proxy', 1, ''), | |
251 qr/status: 400.*backend body:::/s, 'proxy too short'); | |
252 like(http3_get_body_custom('/proxy', 1, '01'), | |
253 qr/status: 400.*backend body:::/s, 'proxy too long'); | |
254 like(http3_get_body_custom('/proxy', 1, '01', more => 1), | |
255 qr/status: 400.*backend body:::/s, 'proxy too long more'); | |
256 | |
257 # error_page 502 after proxy with request buffering disabled | |
258 | |
259 like(http3_get('/unbuf'), | |
260 qr/status: 502.*backend body:::/s, 'unbuf proxy'); | |
261 like(http3_get_body_custom('/unbuf', 10, '0123456789', sleep => 0.1), | |
262 qr/status: 502.*backend body:::/s, 'unbuf proxy small'); | |
263 like(http3_get_body_incomplete('/unbuf', 10000, '0123456789'), | |
264 qr/status: 502.*backend body:::/s, 'unbuf proxy long'); | |
265 like(http3_get_body_nolen('/unbuf', '0123456789'), | |
266 qr/status: 502.*backend body:::/s, 'unbuf proxy nolen'); | |
267 like(http3_get_body_nolen('/unbuf', '0', '123456789'), | |
268 qr/status: 502.*backend body:::/s, 'unbuf proxy nolen multi'); | |
269 like(http3_get_body_incomplete_nolen('/unbuf', 10000, '0123456789'), | |
270 qr/status: 502.*backend body:::/s, 'unbuf proxy nolen long'); | |
271 | |
272 # error_page 400 after proxy with request buffering disabled | |
273 | |
274 like(http3_get_body_custom('/unbuf2', 1, '', sleep => 0.1), | |
275 qr/status: 400.*backend body:::/s, 'unbuf too short'); | |
276 like(http3_get_body_custom('/unbuf2', 1, '01', sleep => 0.1), | |
277 qr/status: 400.*backend body:::/s, 'unbuf too long'); | |
278 like(http3_get_body_custom('/unbuf2', 1, '01', sleep => 0.1, more => 1), | |
279 qr/status: 400.*backend body:::/s, 'unbuf too long more'); | |
280 | |
281 # error_page 413 and $content_length | |
282 # (used in fastcgi_pass, grpc_pass, uwsgi_pass) | |
283 | |
284 like(http3_get('/length'), | |
285 qr/status: 502.*frontend body:::/s, '$content_length'); | |
286 like(http3_get_body('/length', '0123456789'), | |
287 qr/status: 413.*frontend body:::/s, '$content_length small'); | |
288 like(http3_get_body_incomplete('/length', 10000, '0123456789'), | |
289 qr/status: 413.*frontend body:::/s, '$content_length long'); | |
290 like(http3_get_body_nolen('/length', '0123456789'), | |
291 qr/status: 413.*frontend body:::/s, '$content_length nolen'); | |
292 like(http3_get_body_nolen('/length', '0', '123456789'), | |
293 qr/status: 413.*frontend body:::/s, '$content_length nolen multi'); | |
294 like(http3_get_body_incomplete_nolen('/length', 10000, '0123456789'), | |
295 qr/status: 413.*frontend body:::/s, '$content_length nolen long'); | |
296 | |
297 ############################################################################### | |
298 | |
299 sub http3_get { | |
300 my ($uri) = @_; | |
301 | |
302 my $s = Test::Nginx::HTTP3->new(); | |
303 my $sid = $s->new_stream({ path => $uri }); | |
304 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
305 | |
306 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
307 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
308 | |
309 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
310 keys %{$frame->{headers}}) . "\n\n" | |
311 . join("", map { $_->{data} } @data); | |
312 } | |
313 | |
314 sub http3_get_body { | |
315 my ($uri, $body) = @_; | |
316 | |
317 my $s = Test::Nginx::HTTP3->new(); | |
318 my $sid = $s->new_stream({ path => $uri, body => $body }); | |
319 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
320 | |
321 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
322 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
323 | |
324 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
325 keys %{$frame->{headers}}) . "\n\n" | |
326 . join("", map { $_->{data} } @data); | |
327 } | |
328 | |
329 sub http3_get_body_nolen { | |
330 my ($uri, $body, $body2) = @_; | |
331 | |
332 my $s = Test::Nginx::HTTP3->new(); | |
333 my $sid = $s->new_stream({ path => $uri, body_more => 1 }); | |
334 | |
335 if (defined $body2) { | |
336 select undef, undef, undef, 0.1; | |
337 $s->h3_body($body, $sid, { body_more => 1 }); | |
338 select undef, undef, undef, 0.1; | |
339 $s->h3_body($body2, $sid); | |
340 } else { | |
341 select undef, undef, undef, 0.1; | |
342 $s->h3_body($body, $sid); | |
343 } | |
344 | |
345 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
346 | |
347 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
348 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
349 | |
350 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
351 keys %{$frame->{headers}}) . "\n\n" | |
352 . join("", map { $_->{data} } @data); | |
353 } | |
354 | |
355 sub http3_get_body_incomplete { | |
356 my ($uri, $len, $body) = @_; | |
357 | |
358 my $s = Test::Nginx::HTTP3->new(); | |
359 my $sid = $s->new_stream({ | |
360 headers => [ | |
361 { name => ':method', value => 'GET' }, | |
362 { name => ':scheme', value => 'http' }, | |
363 { name => ':path', value => $uri }, | |
364 { name => ':authority', value => 'localhost' }, | |
365 { name => 'content-length', value => $len }, | |
366 ], | |
367 body_more => 1 | |
368 }); | |
369 $s->h3_body($body, $sid, { body_more => 1 }); | |
370 | |
371 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
372 | |
373 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
374 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
375 | |
376 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
377 keys %{$frame->{headers}}) . "\n\n" | |
378 . join("", map { $_->{data} } @data); | |
379 } | |
380 | |
381 sub http3_get_body_incomplete_nolen { | |
382 my ($uri, $body) = @_; | |
383 | |
384 my $s = Test::Nginx::HTTP3->new(); | |
385 my $sid = $s->new_stream({ path => $uri, body_more => 1 }); | |
386 $s->h3_body($body, $sid, { body_more => 1 }); | |
387 | |
388 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
389 | |
390 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
391 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
392 | |
393 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
394 keys %{$frame->{headers}}) . "\n\n" | |
395 . join("", map { $_->{data} } @data); | |
396 } | |
397 | |
398 sub http3_get_body_custom { | |
399 my ($uri, $len, $body, %extra) = @_; | |
400 | |
401 my $s = Test::Nginx::HTTP3->new(); | |
402 my $sid = $s->new_stream({ | |
403 headers => [ | |
404 { name => ':method', value => 'GET' }, | |
405 { name => ':scheme', value => 'http' }, | |
406 { name => ':path', value => $uri }, | |
407 { name => ':authority', value => 'localhost' }, | |
408 { name => 'content-length', value => $len }, | |
409 ], | |
410 body_more => 1 | |
411 }); | |
412 select undef, undef, undef, $extra{sleep} if $extra{sleep}; | |
413 $s->h3_body($body, $sid, { body_more => 1 }); | |
414 $s->h3_body('', $sid) unless $extra{more}; | |
415 | |
416 my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); | |
417 | |
418 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; | |
419 my (@data) = grep { $_->{type} eq "DATA" } @$frames; | |
420 | |
421 return join("\n", map { "$_: " . $frame->{headers}->{$_}; } | |
422 keys %{$frame->{headers}}) . "\n\n" | |
423 . join("", map { $_->{data} } @data); | |
424 } | |
425 | |
426 ############################################################################### |