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