comparison fastcgi_request_buffering_chunked.t @ 542:e7e3ced702f5

Tests: unbuffered request body.
author Sergey Kandaurov <pluknet@nginx.com>
date Mon, 06 Apr 2015 18:02:30 +0300
parents
children dbf8fb0f3d30
comparison
equal deleted inserted replaced
541:53d0d963eb40 542:e7e3ced702f5
1 #!/usr/bin/perl
2
3 # (C) Maxim Dounin
4 # (C) Sergey Kandaurov
5 # (C) Nginx, Inc.
6
7 # Tests for unbuffered request body with fastcgi backend,
8 # chunked transfer-encoding.
9
10 ###############################################################################
11
12 use warnings;
13 use strict;
14
15 use Test::More;
16 use Socket qw/ CRLF /;
17
18 BEGIN { use FindBin; chdir($FindBin::Bin); }
19
20 use lib 'lib';
21 use Test::Nginx;
22
23 ###############################################################################
24
25 select STDERR; $| = 1;
26 select STDOUT; $| = 1;
27
28 eval { require FCGI; };
29 plan(skip_all => 'FCGI not installed') if $@;
30 plan(skip_all => 'win32') if $^O eq 'MSWin32';
31
32 my $t = Test::Nginx->new()->has(qw/http fastcgi rewrite/);
33
34 $t->write_file_expand('nginx.conf', <<'EOF');
35
36 %%TEST_GLOBALS%%
37
38 daemon off;
39
40 events {
41 }
42
43 http {
44 %%TEST_GLOBALS_HTTP%%
45
46 server {
47 listen 127.0.0.1:8080;
48 server_name localhost;
49
50 client_header_buffer_size 1k;
51 fastcgi_request_buffering off;
52 fastcgi_param REQUEST_URI $request_uri;
53
54 location / {
55 client_body_buffer_size 2k;
56 add_header X-Body "$request_body";
57 fastcgi_pass 127.0.0.1:8081;
58 }
59 location /single {
60 client_body_in_single_buffer on;
61 add_header X-Body "$request_body";
62 fastcgi_pass 127.0.0.1:8081;
63 }
64 location /preread {
65 fastcgi_pass 127.0.0.1:8082;
66 }
67 location /error_page {
68 fastcgi_pass 127.0.0.1:8081;
69 error_page 404 /404;
70 fastcgi_intercept_errors on;
71 }
72 location /404 {
73 return 200 "$request_body\n";
74 }
75 }
76 }
77
78 EOF
79
80 $t->run_daemon(\&fastcgi_daemon);
81 $t->try_run('no fastcgi_request_buffering')->plan(19);
82
83 $t->waitforsocket('127.0.0.1:8081');
84
85 ###############################################################################
86
87 unlike(http_get('/'), qr/X-Body:/ms, 'no body');
88
89 like(http_get_body('/', '0123456789'),
90 qr/X-Body: 0123456789\x0d?$/ms, 'body');
91
92 like(http_get_body('/', '0123456789' x 128),
93 qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in two buffers');
94
95 like(http_get_body('/single', '0123456789' x 128),
96 qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer');
97
98 like(http_get_body('/error_page', '0123456789'),
99 qr/^0123456789$/m, 'body in error page');
100
101 # pipelined requests
102
103 like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512,
104 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined');
105 like(http_get_body('/', '0123456789' x 128, '0123456789' x 512, '0123456789',
106 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined 2');
107
108 # interactive tests
109
110 my $s = get_body('/preread', 8082);
111 ok($s, 'no preread');
112
113 SKIP: {
114 skip 'no preread failed', 3 unless $s;
115
116 is($s->{upload}('01234'), '01234', 'no preread - body part');
117 is($s->{upload}('56789', last => 1), '56789', 'no preread - body part 2');
118
119 like($s->{http_end}(), qr/200 OK/, 'no preread - response');
120
121 }
122
123 $s = get_body('/preread', 8082, '01234');
124 ok($s, 'preread');
125
126 SKIP: {
127 skip 'preread failed', 3 unless $s;
128
129 is($s->{preread}, '01234', 'preread - preread');
130 is($s->{upload}('56789', last => 1), '56789', 'preread - body');
131
132 like($s->{http_end}(), qr/200 OK/, 'preread - response');
133
134 }
135
136 $s = get_body('/preread', 8082, '01234', many => 1);
137 ok($s, 'chunks');
138
139 SKIP: {
140 skip 'chunks failed', 3 unless $s;
141
142 is($s->{preread}, '01234many', 'chunks - preread');
143 is($s->{upload}('56789', many => 1, last => 1), '56789many', 'chunks - body');
144
145 like($s->{http_end}(), qr/200 OK/, 'chunks - response');
146
147 }
148
149 ###############################################################################
150
151 sub http_get_body {
152 my $uri = shift;
153 my $last = pop;
154 return http( join '', (map {
155 my $body = $_;
156 "GET $uri HTTP/1.1" . CRLF
157 . "Host: localhost" . CRLF
158 . "Content-Length: " . (length $body) . CRLF . CRLF
159 . $body
160 } @_),
161 "GET $uri HTTP/1.1" . CRLF
162 . "Host: localhost" . CRLF
163 . "Connection: close" . CRLF
164 . "Content-Length: " . (length $last) . CRLF . CRLF
165 . $last
166 );
167 }
168
169 # Simple FastCGI responder implementation.
170
171 # http://www.fastcgi.com/devkit/doc/fcgi-spec.html
172
173 sub fastcgi_read_record($) {
174 my ($buf) = @_;
175
176 my ($n, $h, $header);
177
178 return undef unless length $$buf;
179
180 @{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $$buf);
181
182 $h->{content} = substr $$buf, 8, $h->{clen};
183 $h->{padding} = substr $$buf, 8 + $h->{clen}, $h->{plen};
184
185 $$buf = substr $$buf, 8 + $h->{clen} + $h->{plen};
186
187 return $h;
188 }
189
190 sub fastcgi_respond($$$$) {
191 my ($socket, $version, $id, $body) = @_;
192
193 # stdout
194 $socket->write(pack("CCnnCx", $version, 6, $id, length($body), 8));
195 $socket->write($body);
196 select(undef, undef, undef, 0.1);
197 $socket->write(pack("xxxxxxxx"));
198 select(undef, undef, undef, 0.1);
199
200 # write some text to stdout and stderr split over multiple network
201 # packets to test if we correctly set pipe length in various places
202
203 my $tt = "test text, just for test";
204
205 $socket->write(pack("CCnnCx", $version, 6, $id,
206 length($tt . $tt), 0) . $tt);
207 select(undef, undef, undef, 0.1);
208 $socket->write($tt . pack("CC", $version, 7));
209 select(undef, undef, undef, 0.1);
210 $socket->write(pack("nnCx", $id, length($tt), 0));
211 select(undef, undef, undef, 0.1);
212 $socket->write($tt);
213 select(undef, undef, undef, 0.1);
214
215 # close stdout
216 $socket->write(pack("CCnnCx", $version, 6, $id, 0, 0));
217
218 select(undef, undef, undef, 0.1);
219
220 # end request
221 $socket->write(pack("CCnnCx", $version, 3, $id, 8, 0));
222 select(undef, undef, undef, 0.1);
223 $socket->write(pack("NCxxx", 0, 0));
224 }
225
226 sub get_body {
227 my ($url, $port, $body, %extra) = @_;
228 my ($server, $client, $s);
229 my ($last, $many) = (0, 0);
230 my ($version, $id);
231
232 $last = $extra{last} if defined $extra{last};
233 $many = $extra{many} if defined $extra{many};
234
235 $server = IO::Socket::INET->new(
236 Proto => 'tcp',
237 LocalHost => '127.0.0.1',
238 LocalPort => $port,
239 Listen => 5,
240 Reuse => 1
241 )
242 or die "Can't create listening socket: $!\n";
243
244 my $r = <<EOF;
245 GET $url HTTP/1.1
246 Host: localhost
247 Connection: close
248 Transfer-Encoding: chunked
249
250 EOF
251
252 if (defined $body) {
253 $r .= sprintf("%x", length $body) . CRLF;
254 $r .= $body . CRLF;
255 }
256 if (defined $body && $many) {
257 $r .= sprintf("%x", length 'many') . CRLF;
258 $r .= 'many' . CRLF;
259 }
260 if ($last) {
261 $r .= "0" . CRLF . CRLF;
262 }
263
264 $s = http($r, start => 1);
265
266 eval {
267 local $SIG{ALRM} = sub { die "timeout\n" };
268 local $SIG{PIPE} = sub { die "sigpipe\n" };
269 alarm(5);
270
271 $client = $server->accept();
272
273 alarm(0);
274 };
275 alarm(0);
276 if ($@) {
277 log_in("died: $@");
278 return undef;
279 }
280
281 $client->sysread(my $buf, 1024);
282 $body = '';
283
284 while (my $h = fastcgi_read_record(\$buf)) {
285 $version = $h->{version};
286 $id = $h->{id};
287
288 # skip everything unless stdin
289 next if $h->{type} != 5;
290
291 $body .= $h->{content};
292 }
293
294 my $f = { preread => $body };
295 $f->{upload} = sub {
296 my ($body, %extra) = @_;
297 my ($last, $many) = (0, 0);
298
299 $last = $extra{last} if defined $extra{last};
300 $many = $extra{many} if defined $extra{many};
301
302 my $buf = sprintf("%x", length $body) . CRLF;
303 $buf .= $body . CRLF;
304 if ($many) {
305 $buf .= sprintf("%x", length 'many') . CRLF;
306 $buf .= 'many' . CRLF;
307 }
308 if ($last) {
309 $buf .= "0" . CRLF . CRLF;
310 }
311
312 eval {
313 local $SIG{ALRM} = sub { die "timeout\n" };
314 local $SIG{PIPE} = sub { die "sigpipe\n" };
315 alarm(5);
316
317 $s->write($buf);
318 $client->sysread($buf, 1024);
319 $body = '';
320
321 while (my $h = fastcgi_read_record(\$buf)) {
322
323 # skip everything unless stdin
324 next if $h->{type} != 5;
325
326 $body .= $h->{content};
327 }
328
329 alarm(0);
330 };
331 alarm(0);
332 if ($@) {
333 log_in("died: $@");
334 return undef;
335 }
336
337 return $body;
338 };
339 $f->{http_end} = sub {
340 my $buf = '';
341
342 fastcgi_respond($client, $version, $id, <<EOF);
343 Status: 200 OK
344 Connection: close
345 X-Port: $port
346
347 OK
348 EOF
349
350 $client->close;
351
352 eval {
353 local $SIG{ALRM} = sub { die "timeout\n" };
354 local $SIG{PIPE} = sub { die "sigpipe\n" };
355 alarm(5);
356
357 $s->sysread($buf, 1024);
358 $s->close();
359
360 alarm(0);
361 };
362 alarm(0);
363 if ($@) {
364 log_in("died: $@");
365 return undef;
366 }
367
368 return $buf;
369 };
370 return $f;
371 }
372
373 ###############################################################################
374
375 sub fastcgi_daemon {
376 my $socket = FCGI::OpenSocket('127.0.0.1:8081', 5);
377 my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV,
378 $socket);
379
380 my $count;
381 while( $request->Accept() >= 0 ) {
382 $count++;
383
384 if ($ENV{REQUEST_URI} eq '/stderr') {
385 warn "sample stderr text" x 512;
386 }
387
388 if ($ENV{REQUEST_URI} eq '/error_page') {
389 print "Status: 404 Not Found" . CRLF . CRLF;
390 next;
391 }
392
393 print <<EOF;
394 Location: http://127.0.0.1:8080/redirect
395 Content-Type: text/html
396
397 SEE-THIS
398 $count
399 EOF
400 }
401
402 FCGI::CloseSocket($socket);
403 }
404
405 ###############################################################################