Tests: tests for extra data and short responses.
diff --git a/fastcgi_extra_data.t b/fastcgi_extra_data.t
new file mode 100644
index 0000000..254e1bd
--- /dev/null
+++ b/fastcgi_extra_data.t
@@ -0,0 +1,192 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+# (C) Nginx, Inc.
+
+# Test for fastcgi backend, responses with extra data or premature close.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+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 cache rewrite addition/)->plan(20)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ fastcgi_param REQUEST_URI $request_uri;
+ fastcgi_param REQUEST_METHOD $request_method;
+
+ fastcgi_cache_path cache keys_zone=one:1m;
+ fastcgi_cache_key $request_uri;
+ fastcgi_cache_valid any 1m;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location / {
+ fastcgi_pass 127.0.0.1:8081;
+ add_after_body /after;
+ }
+
+ location /unbuf/ {
+ fastcgi_pass 127.0.0.1:8081;
+ fastcgi_buffering off;
+ add_after_body /after;
+ }
+
+ location /head/ {
+ fastcgi_pass 127.0.0.1:8081;
+ fastcgi_cache one;
+ add_after_body /after;
+ }
+
+ location /after {
+ return 200 ":after\n";
+ }
+ }
+}
+
+EOF
+
+$t->run_daemon(\&fastcgi_daemon);
+$t->run()->waitforsocket('127.0.0.1:' . port(8081));
+
+###############################################################################
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data');
+like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response');
+like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response');
+
+}
+
+like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD');
+like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD');
+like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD');
+
+# unbuffered responses
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/,
+ 'unbuffered with extra data');
+like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'unbuffered too short response');
+like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s,
+ 'unbuffered empty too short responsde');
+
+}
+
+like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered no data in HEAD');
+like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered too short response to HEAD');
+like(http_head('/unbuf/empty'), qr/200 OK/,
+ 'unbuffered empty response to HEAD');
+
+# caching of responsses to HEAD requests
+
+like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body');
+like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching');
+like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra');
+like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short');
+
+like(http_get('/head/empty'), qr/200 OK/, 'head no body cached');
+like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached');
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s,
+ 'head extra cached');
+like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'head too short cached');
+
+}
+
+###############################################################################
+
+sub fastcgi_daemon {
+ my $socket = FCGI::OpenSocket('127.0.0.1:' . port(8081), 5);
+ my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV,
+ $socket);
+
+ my ($uri, $head);
+
+ while( $request->Accept() >= 0 ) {
+ $uri = $ENV{REQUEST_URI};
+ $uri =~ s!^/unbuf!!;
+
+ $head = $ENV{REQUEST_METHOD} eq 'HEAD';
+
+ if ($uri eq '/') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 8\n\n";
+ print "SEE-THIS-BUT-NOT-THIS\n";
+
+ } elsif ($uri eq '/short') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 100\n\n";
+ print "SEE-THIS-TOO-SHORT-RESPONSE\n";
+
+ } elsif ($uri eq '/empty') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 100\n\n";
+
+ } elsif ($uri eq '/head/empty') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 8\n\n";
+ print "SEE-THIS" unless $head;
+
+ } elsif ($uri eq '/head/matching') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 8\n\n";
+ print "SEE-THIS";
+
+ } elsif ($uri eq '/head/extra') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 8\n\n";
+ print "SEE-THIS-BUT-NOT-THIS\n";
+
+ } elsif ($uri eq '/head/short') {
+ print "Content-Type: text/html\n";
+ print "Content-Length: 100\n\n";
+ print "SEE-THIS\n";
+ }
+ }
+
+ FCGI::CloseSocket($socket);
+}
+
+###############################################################################
diff --git a/memcached_fake_extra.t b/memcached_fake_extra.t
new file mode 100644
index 0000000..ea23f3c
--- /dev/null
+++ b/memcached_fake_extra.t
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+# (C) Nginx, Inc.
+
+# Test for memcached backend returning extra data after trailer.
+
+###############################################################################
+
+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 rewrite memcached/)->plan(1)
+ ->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;
+
+ location / {
+ set $memcached_key $uri;
+ memcached_pass 127.0.0.1:8081;
+ }
+ }
+}
+
+EOF
+
+$t->run_daemon(\&memcached_fake_daemon);
+$t->run();
+
+$t->waitforsocket('127.0.0.1:' . port(8081))
+ or die "Can't start fake memcached";
+
+###############################################################################
+
+$t->todo_alerts() unless $t->has_version('1.19.1');
+
+like(http_get('/'), qr/SEE-THIS/, 'memcached data after trailer');
+
+###############################################################################
+
+sub memcached_fake_daemon {
+ my $server = IO::Socket::INET->new(
+ Proto => 'tcp',
+ LocalAddr => '127.0.0.1:' . port(8081),
+ Listen => 5,
+ Reuse => 1
+ )
+ or die "Can't create listening socket: $!\n";
+
+ local $SIG{PIPE} = 'IGNORE';
+
+ while (my $client = $server->accept()) {
+ $client->autoflush(1);
+
+ while (<$client>) {
+ last if (/\x0d\x0a$/);
+ }
+
+ print $client 'VALUE / 0 8' . CRLF;
+ print $client 'SEE-THIS' . CRLF . 'END' . CRLF
+ . "\0" . ("1" x 1024);
+
+ select(undef, undef, undef, 0.2);
+
+ print $client 'EXTRA' . CRLF;
+ close $client;
+ }
+}
+
+###############################################################################
diff --git a/proxy_chunked_extra.t b/proxy_chunked_extra.t
new file mode 100644
index 0000000..af5f873
--- /dev/null
+++ b/proxy_chunked_extra.t
@@ -0,0 +1,120 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+# (C) Nginx, Inc.
+
+# Test for http backend returning response with Transfer-Encoding: chunked,
+# followed by some extra data.
+
+###############################################################################
+
+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/)->plan(1);
+
+$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;
+
+ proxy_buffer_size 128;
+ proxy_buffers 4 128;
+
+ location / {
+ proxy_pass http://127.0.0.1:8081;
+ proxy_read_timeout 1s;
+ }
+ }
+}
+
+EOF
+
+$t->run_daemon(\&http_chunked_daemon);
+$t->run()->waitforsocket('127.0.0.1:' . port(8081));
+
+###############################################################################
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/'), qr/200 OK(?!.*zzz)/s, 'chunked with extra data');
+
+}
+
+###############################################################################
+
+sub http_chunked_daemon {
+ my $server = IO::Socket::INET->new(
+ Proto => 'tcp',
+ LocalAddr => '127.0.0.1:' . port(8081),
+ Listen => 5,
+ Reuse => 1
+ )
+ or die "Can't create listening socket: $!\n";
+
+ local $SIG{PIPE} = 'IGNORE';
+
+ while (my $client = $server->accept()) {
+ $client->autoflush(1);
+
+ while (<$client>) {
+ last if (/^\x0d?\x0a?$/);
+ }
+
+ # return a large response start to allocate
+ # multiple buffers; stop at the buffer end
+
+ print $client ""
+ . "HTTP/1.1 200 OK" . CRLF
+ . "Connection: close" . CRLF
+ . "Transfer-Encoding: chunked" . CRLF . CRLF
+ . "80" . CRLF . ("x" x 126) . CRLF . CRLF
+ . "80" . CRLF . ("x" x 126) . CRLF . CRLF
+ . "80" . CRLF . ("x" x 126) . CRLF . CRLF
+ . "80" . CRLF . ("x" x 126) . CRLF . CRLF
+ . "20" . CRLF . ("x" x 30) . CRLF . CRLF;
+
+ select(undef, undef, undef, 0.3);
+
+ # fill three full buffers here, so they are
+ # processed in order, regardless of the
+ # p->upstream_done flag set
+
+ print $client ""
+ . "75" . CRLF . ("y" x 115) . CRLF . CRLF
+ . "0" . CRLF . CRLF
+ . "75" . CRLF . ("z" x 115) . CRLF . CRLF
+ . "0" . CRLF . CRLF
+ . "75" . CRLF . ("z" x 115) . CRLF . CRLF
+ . "0" . CRLF . CRLF;
+
+ close $client;
+ }
+}
+
+###############################################################################
diff --git a/proxy_extra_data.t b/proxy_extra_data.t
new file mode 100644
index 0000000..22cb824
--- /dev/null
+++ b/proxy_extra_data.t
@@ -0,0 +1,211 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+# (C) Nginx, Inc.
+
+# Tests for http backend with extra data.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+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 cache rewrite addition/)->plan(20)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ proxy_cache_path cache keys_zone=one:1m;
+ proxy_cache_key $request_uri;
+ proxy_cache_valid any 1m;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location / {
+ proxy_pass http://127.0.0.1:8081;
+ add_after_body /after;
+ }
+
+ location /unbuf/ {
+ proxy_pass http://127.0.0.1:8081;
+ proxy_buffering off;
+ add_after_body /after;
+ }
+
+ location /head/ {
+ proxy_pass http://127.0.0.1:8081;
+ proxy_cache one;
+ add_after_body /after;
+ }
+
+ location /after {
+ return 200 ":after\n";
+ }
+ }
+}
+
+EOF
+
+$t->run_daemon(\&http_daemon);
+$t->run()->waitforsocket('127.0.0.1:' . port(8081));
+
+###############################################################################
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data');
+
+}
+
+like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response');
+like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response');
+
+like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD');
+like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD');
+like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD');
+
+# unbuffered responses
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/,
+ 'unbuffered with extra data');
+
+}
+
+like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'unbuffered too short response');
+like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s,
+ 'unbuffered empty too short response');
+
+like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered no data in HEAD');
+like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered too short response to HEAD');
+like(http_head('/unbuf/empty'), qr/200 OK/,
+ 'unbuffered empty response to HEAD');
+
+# caching of responsses to HEAD requests
+
+like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body');
+like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching');
+like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra');
+like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short');
+
+like(http_get('/head/empty'), qr/SEE-THIS/, 'head no body cached');
+like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached');
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s,
+ 'head extra cached');
+
+}
+
+like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'head too short cached');
+
+###############################################################################
+
+sub http_daemon {
+ my $server = IO::Socket::INET->new(
+ Proto => 'tcp',
+ LocalHost => '127.0.0.1:' . port(8081),
+ Listen => 5,
+ Reuse => 1
+ )
+ or die "Can't create listening socket: $!\n";
+
+ local $SIG{PIPE} = 'IGNORE';
+
+ my ($uri, $head);
+
+ while (my $c = $server->accept()) {
+ $c->autoflush(1);
+
+ my $headers = '';
+ my $uri = '';
+
+ while (<$c>) {
+ $headers .= $_;
+ last if (/^\x0d?\x0a?$/);
+ }
+
+ $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+ $uri =~ s!^/unbuf!!;
+
+ $head = ($headers =~ /^HEAD/);
+
+ if ($uri eq '/') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS-BUT-NOT-THIS\n");
+
+ } elsif ($uri eq '/short') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+ $c->print("SEE-THIS-TOO-SHORT-RESPONSE\n");
+
+ } elsif ($uri eq '/empty') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+
+ } elsif ($uri eq '/head/empty') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS") unless $head;
+
+ } elsif ($uri eq '/head/matching') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS");
+
+ } elsif ($uri eq '/head/extra') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS-BUT-NOT-THIS\n");
+
+ } elsif ($uri eq '/head/short') {
+ $c->print("HTTP/1.1 200 OK\n");
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+ $c->print("SEE-THIS\n");
+ }
+
+ close $c;
+ }
+}
+
+###############################################################################
diff --git a/scgi_extra_data.t b/scgi_extra_data.t
new file mode 100644
index 0000000..edecb7d
--- /dev/null
+++ b/scgi_extra_data.t
@@ -0,0 +1,200 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+# (C) Nginx, Inc.
+
+# Test for scgi backend with extra data.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+eval { require SCGI; };
+plan(skip_all => 'SCGI not installed') if $@;
+
+my $t = Test::Nginx->new()
+ ->has(qw/http scgi cache rewrite addition/)->plan(20)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ scgi_param SCGI 1;
+ scgi_param REQUEST_URI $request_uri;
+ scgi_param REQUEST_METHOD $request_method;
+
+ scgi_cache_path cache keys_zone=one:1m;
+ scgi_cache_key $request_uri;
+ scgi_cache_valid any 1m;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location / {
+ scgi_pass 127.0.0.1:8081;
+ add_after_body /after;
+ }
+
+ location /unbuf/ {
+ scgi_pass 127.0.0.1:8081;
+ scgi_buffering off;
+ add_after_body /after;
+ }
+
+ location /head/ {
+ scgi_pass 127.0.0.1:8081;
+ scgi_cache one;
+ add_after_body /after;
+ }
+
+ location /after {
+ return 200 ":after\n";
+ }
+ }
+}
+
+EOF
+
+$t->run_daemon(\&scgi_daemon);
+$t->run()->waitforsocket('127.0.0.1:' . port(8081));
+
+###############################################################################
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data');
+like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response');
+like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response');
+
+}
+
+like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD');
+like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD');
+like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD');
+
+# unbuffered responses
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/,
+ 'unbuffered with extra data');
+like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'unbuffered too short response');
+like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s,
+ 'unbuffered empty too short response');
+
+}
+
+like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered no data in HEAD');
+like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s,
+ 'unbuffered too short response to HEAD');
+like(http_head('/unbuf/empty'), qr/200 OK/,
+ 'unbuffered empty response to HEAD');
+
+# caching of responsses to HEAD requests
+
+like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body');
+like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching');
+like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra');
+like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short');
+
+like(http_get('/head/empty'), qr/SEE-THIS/, 'head no body cached');
+like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached');
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.19.1');
+
+like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s,
+ 'head extra cached');
+like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s,
+ 'head too short cached');
+
+}
+
+###############################################################################
+
+sub scgi_daemon {
+ my $server = IO::Socket::INET->new(
+ Proto => 'tcp',
+ LocalHost => '127.0.0.1:' . port(8081),
+ Listen => 5,
+ Reuse => 1
+ )
+ or die "Can't create listening socket: $!\n";
+
+ my $scgi = SCGI->new($server, blocking => 1);
+ my ($c, $uri, $head);
+
+ while (my $request = $scgi->accept()) {
+ eval { $request->read_env(); };
+ next if $@;
+
+ $uri = $request->env->{REQUEST_URI};
+ $uri =~ s!^/unbuf!!;
+
+ $head = $request->env->{REQUEST_METHOD} eq 'HEAD';
+
+ $c = $request->connection();
+
+ if ($uri eq '/') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS-BUT-NOT-THIS\n");
+
+ } elsif ($uri eq '/short') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+ $c->print("SEE-THIS-TOO-SHORT-RESPONSE\n");
+
+ } elsif ($uri eq '/empty') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+
+ } elsif ($uri eq '/head/empty') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS") unless $head;
+
+ } elsif ($uri eq '/head/matching') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS");
+
+ } elsif ($uri eq '/head/extra') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 8\n\n");
+ $c->print("SEE-THIS-BUT-NOT-THIS\n");
+
+ } elsif ($uri eq '/head/short') {
+ $c->print("Content-Type: text/html\n");
+ $c->print("Content-Length: 100\n\n");
+ $c->print("SEE-THIS\n");
+ }
+ }
+}
+
+###############################################################################