# HG changeset patch # User Sergey Kandaurov # Date 1428332550 -10800 # Node ID e7e3ced702f5c5eb055592540b83819641ec86e7 # Parent 53d0d963eb40b51067d575f2c032e5871341c8d7 Tests: unbuffered request body. diff --git a/fastcgi_request_buffering.t b/fastcgi_request_buffering.t new file mode 100644 --- /dev/null +++ b/fastcgi_request_buffering.t @@ -0,0 +1,364 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for unbuffered request body with fastcgi backend. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require FCGI; }; +plan(skip_all => 'FCGI not installed') if $@; +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +my $t = Test::Nginx->new()->has(qw/http fastcgi rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + client_header_buffer_size 1k; + fastcgi_request_buffering off; + fastcgi_param REQUEST_URI $request_uri; + + location / { + client_body_buffer_size 2k; + add_header X-Body "$request_body"; + fastcgi_pass 127.0.0.1:8081; + } + location /single { + client_body_in_single_buffer on; + add_header X-Body "$request_body"; + fastcgi_pass 127.0.0.1:8081; + } + location /preread { + fastcgi_pass 127.0.0.1:8082; + } + location /error_page { + fastcgi_pass 127.0.0.1:8081; + error_page 404 /404; + fastcgi_intercept_errors on; + } + location /404 { + return 200 "$request_body\n"; + } + } +} + +EOF + +$t->run_daemon(\&fastcgi_daemon); +$t->try_run('no fastcgi_request_buffering')->plan(15); + +$t->waitforsocket('127.0.0.1:8081'); + +############################################################################### + +unlike(http_get('/'), qr/X-Body:/ms, 'no body'); + +like(http_get_body('/', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'body'); + +like(http_get_body('/', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in two buffers'); + +like(http_get_body('/single', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); + +like(http_get_body('/error_page', '0123456789'), + qr/^0123456789$/m, 'body in error page'); + +# pipelined requests + +like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined'); +like(http_get_body('/', '0123456789' x 128, '0123456789' x 512, '0123456789', + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined 2'); + +# interactive tests + +my $s = get_body('/preread', 8082, 10); +ok($s, 'no preread'); + +SKIP: { +skip 'no preread failed', 3 unless $s; + +is($s->{upload}('01234'), '01234', 'no preread - body part'); +is($s->{upload}('56789'), '56789', 'no preread - body part 2'); + +like($s->{http_end}(), qr/200 OK/, 'no preread - response'); + +} + +$s = get_body('/preread', 8082, 10, '01234'); +ok($s, 'preread'); + +SKIP: { +skip 'preread failed', 3 unless $s; + +is($s->{preread}, '01234', 'preread - preread'); +is($s->{upload}('56789'), '56789', 'preread - body'); + +like($s->{http_end}(), qr/200 OK/, 'preread - response'); + +} + +############################################################################### + +sub http_get_body { + my $uri = shift; + my $last = pop; + return http( join '', (map { + my $body = $_; + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Content-Length: " . (length $body) . CRLF . CRLF + . $body + } @_), + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Connection: close" . CRLF + . "Content-Length: " . (length $last) . CRLF . CRLF + . $last + ); +} + +# Simple FastCGI responder implementation. + +# http://www.fastcgi.com/devkit/doc/fcgi-spec.html + +sub fastcgi_read_record($) { + my ($buf) = @_; + + my ($n, $h, $header); + + return undef unless length $$buf; + + @{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $$buf); + + $h->{content} = substr $$buf, 8, $h->{clen}; + $h->{padding} = substr $$buf, 8 + $h->{clen}, $h->{plen}; + + $$buf = substr $$buf, 8 + $h->{clen} + $h->{plen}; + + return $h; +} + +sub fastcgi_respond($$$$) { + my ($socket, $version, $id, $body) = @_; + + # stdout + $socket->write(pack("CCnnCx", $version, 6, $id, length($body), 8)); + $socket->write($body); + select(undef, undef, undef, 0.1); + $socket->write(pack("xxxxxxxx")); + select(undef, undef, undef, 0.1); + + # write some text to stdout and stderr split over multiple network + # packets to test if we correctly set pipe length in various places + + my $tt = "test text, just for test"; + + $socket->write(pack("CCnnCx", $version, 6, $id, + length($tt . $tt), 0) . $tt); + select(undef, undef, undef, 0.1); + $socket->write($tt . pack("CC", $version, 7)); + select(undef, undef, undef, 0.1); + $socket->write(pack("nnCx", $id, length($tt), 0)); + select(undef, undef, undef, 0.1); + $socket->write($tt); + select(undef, undef, undef, 0.1); + + # close stdout + $socket->write(pack("CCnnCx", $version, 6, $id, 0, 0)); + + select(undef, undef, undef, 0.1); + + # end request + $socket->write(pack("CCnnCx", $version, 3, $id, 8, 0)); + select(undef, undef, undef, 0.1); + $socket->write(pack("NCxxx", 0, 0)); +} + +sub get_body { + my ($url, $port, $length, $body) = @_; + my ($server, $client, $s); + my ($version, $id); + + $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $r = < 1); + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $client = $server->accept(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + $client->sysread(my $buf, 1024); + $body = ''; + + while (my $h = fastcgi_read_record(\$buf)) { + $version = $h->{version}; + $id = $h->{id}; + + # skip everything unless stdin + next if $h->{type} != 5; + + $body .= $h->{content}; + } + + my $f = { preread => $body }; + $f->{upload} = sub { + my $buf = shift; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->write($buf); + $client->sysread($buf, 1024); + $body = ''; + + while (my $h = fastcgi_read_record(\$buf)) { + + # skip everything unless stdin + next if $h->{type} != 5; + + $body .= $h->{content}; + } + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $body; + }; + $f->{http_end} = sub { + my $buf = ''; + + fastcgi_respond($client, $version, $id, <close; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->sysread($buf, 1024); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + return $f; +} + +############################################################################### + +sub fastcgi_daemon { + my $socket = FCGI::OpenSocket('127.0.0.1:8081', 5); + my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, + $socket); + + my $count; + while( $request->Accept() >= 0 ) { + $count++; + + if ($ENV{REQUEST_URI} eq '/stderr') { + warn "sample stderr text" x 512; + } + + if ($ENV{REQUEST_URI} eq '/error_page') { + print "Status: 404 Not Found" . CRLF . CRLF; + next; + } + + print < 'FCGI not installed') if $@; +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +my $t = Test::Nginx->new()->has(qw/http fastcgi rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + client_header_buffer_size 1k; + fastcgi_request_buffering off; + fastcgi_param REQUEST_URI $request_uri; + + location / { + client_body_buffer_size 2k; + add_header X-Body "$request_body"; + fastcgi_pass 127.0.0.1:8081; + } + location /single { + client_body_in_single_buffer on; + add_header X-Body "$request_body"; + fastcgi_pass 127.0.0.1:8081; + } + location /preread { + fastcgi_pass 127.0.0.1:8082; + } + location /error_page { + fastcgi_pass 127.0.0.1:8081; + error_page 404 /404; + fastcgi_intercept_errors on; + } + location /404 { + return 200 "$request_body\n"; + } + } +} + +EOF + +$t->run_daemon(\&fastcgi_daemon); +$t->try_run('no fastcgi_request_buffering')->plan(19); + +$t->waitforsocket('127.0.0.1:8081'); + +############################################################################### + +unlike(http_get('/'), qr/X-Body:/ms, 'no body'); + +like(http_get_body('/', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'body'); + +like(http_get_body('/', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in two buffers'); + +like(http_get_body('/single', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); + +like(http_get_body('/error_page', '0123456789'), + qr/^0123456789$/m, 'body in error page'); + +# pipelined requests + +like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined'); +like(http_get_body('/', '0123456789' x 128, '0123456789' x 512, '0123456789', + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined 2'); + +# interactive tests + +my $s = get_body('/preread', 8082); +ok($s, 'no preread'); + +SKIP: { +skip 'no preread failed', 3 unless $s; + +is($s->{upload}('01234'), '01234', 'no preread - body part'); +is($s->{upload}('56789', last => 1), '56789', 'no preread - body part 2'); + +like($s->{http_end}(), qr/200 OK/, 'no preread - response'); + +} + +$s = get_body('/preread', 8082, '01234'); +ok($s, 'preread'); + +SKIP: { +skip 'preread failed', 3 unless $s; + +is($s->{preread}, '01234', 'preread - preread'); +is($s->{upload}('56789', last => 1), '56789', 'preread - body'); + +like($s->{http_end}(), qr/200 OK/, 'preread - response'); + +} + +$s = get_body('/preread', 8082, '01234', many => 1); +ok($s, 'chunks'); + +SKIP: { +skip 'chunks failed', 3 unless $s; + +is($s->{preread}, '01234many', 'chunks - preread'); +is($s->{upload}('56789', many => 1, last => 1), '56789many', 'chunks - body'); + +like($s->{http_end}(), qr/200 OK/, 'chunks - response'); + +} + +############################################################################### + +sub http_get_body { + my $uri = shift; + my $last = pop; + return http( join '', (map { + my $body = $_; + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Content-Length: " . (length $body) . CRLF . CRLF + . $body + } @_), + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Connection: close" . CRLF + . "Content-Length: " . (length $last) . CRLF . CRLF + . $last + ); +} + +# Simple FastCGI responder implementation. + +# http://www.fastcgi.com/devkit/doc/fcgi-spec.html + +sub fastcgi_read_record($) { + my ($buf) = @_; + + my ($n, $h, $header); + + return undef unless length $$buf; + + @{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $$buf); + + $h->{content} = substr $$buf, 8, $h->{clen}; + $h->{padding} = substr $$buf, 8 + $h->{clen}, $h->{plen}; + + $$buf = substr $$buf, 8 + $h->{clen} + $h->{plen}; + + return $h; +} + +sub fastcgi_respond($$$$) { + my ($socket, $version, $id, $body) = @_; + + # stdout + $socket->write(pack("CCnnCx", $version, 6, $id, length($body), 8)); + $socket->write($body); + select(undef, undef, undef, 0.1); + $socket->write(pack("xxxxxxxx")); + select(undef, undef, undef, 0.1); + + # write some text to stdout and stderr split over multiple network + # packets to test if we correctly set pipe length in various places + + my $tt = "test text, just for test"; + + $socket->write(pack("CCnnCx", $version, 6, $id, + length($tt . $tt), 0) . $tt); + select(undef, undef, undef, 0.1); + $socket->write($tt . pack("CC", $version, 7)); + select(undef, undef, undef, 0.1); + $socket->write(pack("nnCx", $id, length($tt), 0)); + select(undef, undef, undef, 0.1); + $socket->write($tt); + select(undef, undef, undef, 0.1); + + # close stdout + $socket->write(pack("CCnnCx", $version, 6, $id, 0, 0)); + + select(undef, undef, undef, 0.1); + + # end request + $socket->write(pack("CCnnCx", $version, 3, $id, 8, 0)); + select(undef, undef, undef, 0.1); + $socket->write(pack("NCxxx", 0, 0)); +} + +sub get_body { + my ($url, $port, $body, %extra) = @_; + my ($server, $client, $s); + my ($last, $many) = (0, 0); + my ($version, $id); + + $last = $extra{last} if defined $extra{last}; + $many = $extra{many} if defined $extra{many}; + + $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $r = < 1); + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $client = $server->accept(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + $client->sysread(my $buf, 1024); + $body = ''; + + while (my $h = fastcgi_read_record(\$buf)) { + $version = $h->{version}; + $id = $h->{id}; + + # skip everything unless stdin + next if $h->{type} != 5; + + $body .= $h->{content}; + } + + my $f = { preread => $body }; + $f->{upload} = sub { + my ($body, %extra) = @_; + my ($last, $many) = (0, 0); + + $last = $extra{last} if defined $extra{last}; + $many = $extra{many} if defined $extra{many}; + + my $buf = sprintf("%x", length $body) . CRLF; + $buf .= $body . CRLF; + if ($many) { + $buf .= sprintf("%x", length 'many') . CRLF; + $buf .= 'many' . CRLF; + } + if ($last) { + $buf .= "0" . CRLF . CRLF; + } + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->write($buf); + $client->sysread($buf, 1024); + $body = ''; + + while (my $h = fastcgi_read_record(\$buf)) { + + # skip everything unless stdin + next if $h->{type} != 5; + + $body .= $h->{content}; + } + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $body; + }; + $f->{http_end} = sub { + my $buf = ''; + + fastcgi_respond($client, $version, $id, <close; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->sysread($buf, 1024); + $s->close(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + return $f; +} + +############################################################################### + +sub fastcgi_daemon { + my $socket = FCGI::OpenSocket('127.0.0.1:8081', 5); + my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, + $socket); + + my $count; + while( $request->Accept() >= 0 ) { + $count++; + + if ($ENV{REQUEST_URI} eq '/stderr') { + warn "sample stderr text" x 512; + } + + if ($ENV{REQUEST_URI} eq '/error_page') { + print "Status: 404 Not Found" . CRLF . CRLF; + next; + } + + print <new()->has(qw/http proxy rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + client_header_buffer_size 1k; + proxy_request_buffering off; + + location / { + client_body_buffer_size 2k; + add_header X-Body "$request_body"; + proxy_pass http://127.0.0.1:8081; + } + location /small { + client_body_in_file_only on; + proxy_pass http://127.0.0.1:8080/; + } + location /single { + client_body_in_single_buffer on; + add_header X-Body "$request_body"; + proxy_pass http://127.0.0.1:8081; + } + location /discard { + return 200 "TEST\n"; + } + location /preread { + proxy_pass http://127.0.0.1:8082/; + } + location /error_page { + proxy_pass http://127.0.0.1:8081/404; + error_page 404 /404; + proxy_intercept_errors on; + } + location /404 { + return 200 "$request_body\n"; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + return 204; + } + location /404 { } + } +} + +EOF + +$t->try_run('no proxy_request_buffering')->plan(18); + +############################################################################### + +unlike(http_get('/'), qr/X-Body:/ms, 'no body'); + +like(http_get_body('/', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'body'); + +like(http_get_body('/', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in two buffers'); + +like(http_get_body('/single', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); + +like(http_get_body('/error_page', '0123456789'), + qr/^0123456789$/m, 'body in error page'); + +# pipelined requests + +like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined'); +like(http_get_body('/', '0123456789' x 128, '0123456789' x 512, '0123456789', + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined 2'); + +like(http_get_body('/discard', '0123456789', '0123456789' x 128, + '0123456789' x 512, 'foobar'), qr/(TEST.*){4}/ms, + 'body discard'); +like(http_get_body('/discard', '0123456789' x 128, '0123456789' x 512, + '0123456789', 'foobar'), qr/(TEST.*){4}/ms, + 'body discard 2'); + +# proxy with file only is disabled in unbuffered mode + +like(http_get_body('/small', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'small body in file only'); + +# interactive tests + +my $s = get_body('/preread', 8082, 10); +ok($s, 'no preread'); + +SKIP: { +skip 'no preread failed', 3 unless $s; + +is($s->{upload}('01234'), '01234', 'no preread - body part'); +is($s->{upload}('56789'), '56789', 'no preread - body part 2'); + +like($s->{http_end}(), qr/200 OK/, 'no preread - response'); + +} + +$s = get_body('/preread', 8082, 10, '01234'); +ok($s, 'preread'); + +SKIP: { +skip 'preread failed', 3 unless $s; + +is($s->{preread}, '01234', 'preread - preread'); +is($s->{upload}('56789'), '56789', 'preread - body'); + +like($s->{http_end}(), qr/200 OK/, 'preread - response'); + +} + +############################################################################### + +sub http_get_body { + my $uri = shift; + my $last = pop; + return http( join '', (map { + my $body = $_; + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Content-Length: " . (length $body) . CRLF . CRLF + . $body + } @_), + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Connection: close" . CRLF + . "Content-Length: " . (length $last) . CRLF . CRLF + . $last + ); +} + +sub get_body { + my ($url, $port, $length, $body) = @_; + my ($server, $client, $s); + + $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $r = < 1); + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $client = $server->accept(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + $client->sysread(my $buf, 1024); + $buf =~ s/.*?\x0d\x0a?\x0d\x0a?(.*)/$1/ms; + + my $f = { preread => $buf }; + $f->{upload} = sub { + my $buf = shift; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->write($buf); + $client->sysread($buf, 1024); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + $f->{http_end} = sub { + my $buf = ''; + + $client->write(<close; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->sysread($buf, 1024); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + return $f; +} + +############################################################################### diff --git a/proxy_request_buffering_chunked.t b/proxy_request_buffering_chunked.t new file mode 100644 --- /dev/null +++ b/proxy_request_buffering_chunked.t @@ -0,0 +1,334 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for unbuffered request body, chunked transfer-encoding. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + client_header_buffer_size 1k; + proxy_request_buffering off; + proxy_http_version 1.1; + + location / { + client_body_buffer_size 2k; + add_header X-Body "$request_body"; + proxy_pass http://127.0.0.1:8081; + } + location /small { + client_body_in_file_only on; + proxy_pass http://127.0.0.1:8080/; + } + location /single { + client_body_in_single_buffer on; + add_header X-Body "$request_body"; + proxy_pass http://127.0.0.1:8081; + } + location /discard { + return 200 "TEST\n"; + } + location /preread { + proxy_pass http://127.0.0.1:8082/; + } + location /error_page { + proxy_pass http://127.0.0.1:8081/404; + error_page 404 /404; + proxy_intercept_errors on; + } + location /404 { + return 200 "$request_body\n"; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + return 204; + } + location /404 { } + } +} + +EOF + +$t->try_run('no proxy_request_buffering')->plan(22); + +############################################################################### + +unlike(http_get('/'), qr/X-Body:/ms, 'no body'); + +like(http_get_body('/', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'body'); + +like(http_get_body('/', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in two buffers'); + +like(http_get_body('/single', '0123456789' x 128), + qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); + +like(http_get_body('/error_page', '0123456789'), + qr/^0123456789$/m, 'body in error page'); + +# pipelined requests + +like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined'); +like(http_get_body('/', '0123456789' x 128, '0123456789' x 512, '0123456789', + 'foobar'), qr/X-Body: foobar\x0d?$/ms, 'body pipelined 2'); + +like(http_get_body('/discard', '0123456789', '0123456789' x 128, + '0123456789' x 512, 'foobar'), qr/(TEST.*){4}/ms, + 'body discard'); +like(http_get_body('/discard', '0123456789' x 128, '0123456789' x 512, + '0123456789', 'foobar'), qr/(TEST.*){4}/ms, + 'body discard 2'); + +# proxy with file only is disabled in unbuffered mode + +like(http_get_body('/small', '0123456789'), + qr/X-Body: 0123456789\x0d?$/ms, 'small body in file only'); + +# interactive tests + +my $s = get_body('/preread', 8082); +ok($s, 'no preread'); + +SKIP: { +skip 'no preread failed', 3 unless $s; + +is($s->{upload}('01234'), '5' . CRLF . '01234' . CRLF, + 'no preread - body part'); +is($s->{upload}('56789', last => 1), + '5' . CRLF . '56789' . CRLF . '0' . CRLF . CRLF, + 'no preread - body part 2'); + +like($s->{http_end}(), qr/200 OK/, 'no preread - response'); + +} + +$s = get_body('/preread', 8082, '01234'); +ok($s, 'preread'); + +SKIP: { +skip 'preread failed', 3 unless $s; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.7.12'); + +is($s->{preread}, '5' . CRLF . '01234' . CRLF, 'preread - preread'); + +} + +is($s->{upload}('56789', last => 1), + '5' . CRLF . '56789' . CRLF . '0' . CRLF . CRLF, 'preread - body'); + +like($s->{http_end}(), qr/200 OK/, 'preread - response'); + +} + +$s = get_body('/preread', 8082, '01234', many => 1); +ok($s, 'chunks'); + +SKIP: { +skip 'chunks failed', 3 unless $s; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.7.12'); + +is($s->{preread}, '9' . CRLF . '01234many' . CRLF, 'chunks - preread'); + +} + +is($s->{upload}('56789', many => 1, last => 1), + '9' . CRLF . '56789many' . CRLF . '0' . CRLF . CRLF, 'chunks - body'); + +like($s->{http_end}(), qr/200 OK/, 'chunks - response'); + +} + +############################################################################### + +sub http_get_body { + my $uri = shift; + my $last = pop; + return http( join '', (map { + my $body = $_; + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Content-Length: " . (length $body) . CRLF . CRLF + . $body + } @_), + "GET $uri HTTP/1.1" . CRLF + . "Host: localhost" . CRLF + . "Connection: close" . CRLF + . "Content-Length: " . (length $last) . CRLF . CRLF + . $last + ); +} + +sub get_body { + my ($url, $port, $body, %extra) = @_; + my ($server, $client, $s); + my ($last, $many) = (0, 0); + + $last = $extra{last} if defined $extra{last}; + $many = $extra{many} if defined $extra{many}; + + $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $r = < 1); + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $client = $server->accept(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + $client->sysread(my $buf, 1024); + $buf =~ s/.*?\x0d\x0a?\x0d\x0a?(.*)/$1/ms; + + my $f = { preread => $buf }; + $f->{upload} = sub { + my ($body, %extra) = @_; + my ($last, $many) = (0, 0); + + $last = $extra{last} if defined $extra{last}; + $many = $extra{many} if defined $extra{many}; + + my $buf = sprintf("%x", length $body) . CRLF; + $buf .= $body . CRLF; + if ($many) { + $buf .= sprintf("%x", length 'many') . CRLF; + $buf .= 'many' . CRLF; + } + if ($last) { + $buf .= "0" . CRLF . CRLF; + } + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->write($buf); + $client->sysread($buf, 1024); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + $f->{http_end} = sub { + my $buf = ''; + + $client->write(<close; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(5); + + $s->sysread($buf, 1024); + $s->close(); + + alarm(0); + }; + alarm(0); + if ($@) { + log_in("died: $@"); + return undef; + } + + return $buf; + }; + return $f; +} + +###############################################################################