Mercurial > hg > nginx-tests
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 ############################################################################### |