| #!/usr/bin/perl | |
| # (C) Maxim Dounin | |
| # Tests for proxy with keepalive. | |
| ############################################################################### | |
| use warnings; | |
| use strict; | |
| use Test::More; | |
| use IO::Socket::INET; | |
| 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 upstream_keepalive ssi rewrite/) | |
| ->plan(49)->write_file_expand('nginx.conf', <<'EOF'); | |
| %%TEST_GLOBALS%% | |
| daemon off; | |
| events { | |
| } | |
| http { | |
| %%TEST_GLOBALS_HTTP%% | |
| upstream backend { | |
| server 127.0.0.1:8081; | |
| keepalive 1; | |
| } | |
| server { | |
| listen 127.0.0.1:8080; | |
| server_name localhost; | |
| proxy_http_version 1.1; | |
| proxy_set_header Connection ""; | |
| location / { | |
| proxy_pass http://backend; | |
| } | |
| location /unbuffered/ { | |
| proxy_pass http://backend; | |
| proxy_buffering off; | |
| } | |
| location /inmemory/ { | |
| ssi on; | |
| rewrite ^ /ssi.html break; | |
| } | |
| } | |
| } | |
| EOF | |
| $t->write_file('ssi.html', | |
| '<!--#include virtual="/include$request_uri" set="x" -->' . | |
| 'set: <!--#echo var="x" -->'); | |
| $t->run_daemon(\&http_daemon); | |
| $t->run(); | |
| $t->waitforsocket('127.0.0.1:' . port(8081)) | |
| or die "Can't start test backend"; | |
| ############################################################################### | |
| # There are 3 mostly independent modes of upstream operation: | |
| # | |
| # 1. Buffered, i.e. normal mode with "proxy_buffering on;" | |
| # 2. Unbuffered, i.e. "proxy_buffering off;". | |
| # 3. In memory, i.e. ssi <!--#include ... set --> | |
| # | |
| # These all should be tested. | |
| my ($r, $n); | |
| # buffered | |
| like($r = http_get('/buffered/length1'), qr/SEE-THIS/, 'buffered'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/length2'), qr/X-Connection: $n.*SEE/ms, 'buffered 2'); | |
| like($r = http_get('/buffered/chunked1'), qr/SEE-THIS/, 'buffered chunked'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/chunked2'), qr/X-Connection: $n/, | |
| 'buffered chunked 2'); | |
| like($r = http_get('/buffered/complex1'), qr/(0123456789){100}/, | |
| 'buffered complex chunked'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/complex2'), qr/X-Connection: $n/, | |
| 'buffered complex chunked 2'); | |
| like($r = http_get('/buffered/chunk01'), qr/200 OK/, 'buffered 0 chunk'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/chunk02'), qr/X-Connection: $n/, 'buffered 0 chunk 2'); | |
| like($r = http_head('/buffered/length/head1'), qr/(?!SEE-THIS)/, | |
| 'buffered head'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_head('/buffered/length/head2'), qr/X-Connection: $n/, | |
| 'buffered head 2'); | |
| like($r = http_get('/buffered/empty1'), qr/200 OK/, 'buffered empty'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/empty2'), qr/X-Connection: $n/, 'buffered empty 2'); | |
| like($r = http_get('/buffered/304nolen1'), qr/304 Not/, 'buffered 304'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/304nolen2'), qr/X-Connection: $n/, 'buffered 304 2'); | |
| like($r = http_get('/buffered/304len1'), qr/304 Not/, | |
| 'buffered 304 with length'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/buffered/304len2'), qr/X-Connection: $n/, | |
| 'buffered 304 with length 2'); | |
| # unbuffered | |
| like($r = http_get('/unbuffered/length1'), qr/SEE-THIS/, 'unbuffered'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/length2'), qr/X-Connection: $n/, 'unbuffered 2'); | |
| like($r = http_get('/unbuffered/chunked1'), qr/SEE-THIS/, 'unbuffered chunked'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/chunked2'), qr/X-Connection: $n/, | |
| 'unbuffered chunked 2'); | |
| like($r = http_get('/unbuffered/complex1'), qr/(0123456789){100}/, | |
| 'unbuffered complex chunked'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/complex2'), qr/X-Connection: $n/, | |
| 'unbuffered complex chunked 2'); | |
| like($r = http_get('/unbuffered/chunk01'), qr/200 OK/, 'unbuffered 0 chunk'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/chunk02'), qr/X-Connection: $n/, | |
| 'unbuffered 0 chunk 2'); | |
| like($r = http_get('/unbuffered/empty1'), qr/200 OK/, 'unbuffered empty'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/empty2'), qr/X-Connection: $n/, | |
| 'unbuffered empty 2'); | |
| like($r = http_head('/unbuffered/length/head1'), qr/(?!SEE-THIS)/, | |
| 'unbuffered head'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_head('/unbuffered/length/head2'), qr/X-Connection: $n/, | |
| 'unbuffered head 2'); | |
| like($r = http_get('/unbuffered/304nolen1'), qr/304 Not/, 'unbuffered 304'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/304nolen2'), qr/X-Connection: $n/, | |
| 'unbuffered 304 2'); | |
| like($r = http_get('/unbuffered/304len1'), qr/304 Not/, | |
| 'unbuffered 304 with length'); | |
| $r =~ m/X-Connection: (\d+)/; $n = $1; | |
| like(http_get('/unbuffered/304len2'), qr/X-Connection: $n/, | |
| 'unbuffered 304 with length 2'); | |
| # in memory | |
| like($r = http_get('/inmemory/length1'), qr/SEE-THIS/, 'inmemory'); | |
| $r =~ m/SEE-THIS(\d+)/; $n = $1; | |
| like(http_get('/inmemory/length2'), qr/SEE-THIS$n/, 'inmemory 2'); | |
| like($r = http_get('/inmemory/empty1'), qr/200 OK/, 'inmemory empty'); | |
| $r =~ m/SEE-THIS(\d+)/; $n = $1; | |
| like(http_get('/inmemory/empty2'), qr/200 OK/, 'inmemory empty 2'); | |
| like($r = http_get('/inmemory/chunked1'), qr/SEE-THIS/, 'inmemory chunked'); | |
| $r =~ m/SEE-THIS(\d+)/; $n = $1; | |
| like(http_get('/inmemory/chunked2'), qr/SEE-THIS$n/, 'inmemory chunked 2'); | |
| like($r = http_get('/inmemory/complex1'), qr/(0123456789){100}/, | |
| 'inmemory complex chunked'); | |
| $r =~ m/SEE-THIS(\d+)/; $n = $1; | |
| like(http_get('/inmemory/complex2'), qr/SEE-THIS$n/, | |
| 'inmemory complex chunked 2'); | |
| like(http_get('/inmemory/chunk01'), qr/set: $/, 'inmemory 0 chunk'); | |
| like(http_get('/inmemory/chunk02'), qr/set: $/, 'inmemory 0 chunk 2'); | |
| # closed connection tests | |
| like(http_get('/buffered/closed1'), qr/200 OK/, 'buffered closed 1'); | |
| like(http_get('/buffered/closed2'), qr/200 OK/, 'buffered closed 2'); | |
| like(http_get('/unbuffered/closed1'), qr/200 OK/, 'unbuffered closed 1'); | |
| like(http_get('/unbuffered/closed2'), qr/200 OK/, 'unbuffered closed 2'); | |
| like(http_get('/inmemory/closed1'), qr/200 OK/, 'inmemory closed 1'); | |
| like(http_get('/inmemory/closed2'), qr/200 OK/, 'inmemory closed 2'); | |
| # check for errors, shouldn't be any | |
| like(`grep -F '[error]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no errors'); | |
| ############################################################################### | |
| 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"; | |
| my $ccount = 0; | |
| my $rcount = 0; | |
| # dumb server which is able to keep connections alive | |
| while (my $client = $server->accept()) { | |
| Test::Nginx::log_core('||', | |
| "connection from " . $client->peerhost()); | |
| $client->autoflush(1); | |
| $ccount++; | |
| while (1) { | |
| my $headers = ''; | |
| my $uri = ''; | |
| while (<$client>) { | |
| Test::Nginx::log_core('||', $_); | |
| $headers .= $_; | |
| last if (/^\x0d?\x0a?$/); | |
| } | |
| last if $headers eq ''; | |
| $rcount++; | |
| $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; | |
| if ($uri =~ m/length/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Content-Length: 26" . CRLF . CRLF; | |
| print $client "TEST-OK-IF-YOU-SEE-THIS" . | |
| sprintf("%03d", $ccount) | |
| unless $headers =~ /^HEAD/i; | |
| } elsif ($uri =~ m/empty/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Content-Length: 0" . CRLF . CRLF; | |
| } elsif ($uri =~ m/304nolen/) { | |
| print $client | |
| "HTTP/1.1 304 Not Modified" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . CRLF; | |
| } elsif ($uri =~ m/304len/) { | |
| print $client | |
| "HTTP/1.1 304 Not Modified" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Content-Length: 100" . CRLF . CRLF; | |
| } elsif ($uri =~ m/chunked/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Transfer-Encoding: chunked" . CRLF . | |
| CRLF; | |
| print $client | |
| "1a" . CRLF . | |
| "TEST-OK-IF-YOU-SEE-THIS" . | |
| sprintf("%03d", $ccount) . CRLF . | |
| "0" . CRLF . CRLF | |
| unless $headers =~ /^HEAD/i; | |
| } elsif ($uri =~ m/complex/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Transfer-Encoding: chunked" . CRLF . | |
| CRLF; | |
| if ($headers !~ /^HEAD/i) { | |
| for my $n (1..100) { | |
| print $client | |
| "a" . CRLF . | |
| "0123456789" . CRLF; | |
| select undef, undef, undef, 0.01 | |
| if $n % 50 == 0; | |
| } | |
| print $client | |
| "1a" . CRLF . | |
| "TEST-OK-IF-YOU-SEE-THIS" . | |
| sprintf("%03d", $ccount) . | |
| CRLF . | |
| "0" . CRLF; | |
| select undef, undef, undef, 0.05; | |
| print $client CRLF; | |
| } | |
| } elsif ($uri =~ m/chunk0/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Transfer-Encoding: chunked" . CRLF . | |
| CRLF; | |
| print $client | |
| "0" . CRLF . CRLF | |
| unless $headers =~ /^HEAD/i; | |
| } elsif ($uri =~ m/closed/) { | |
| print $client | |
| "HTTP/1.1 200 OK" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Connection: close" . CRLF . | |
| "Content-Length: 12" . CRLF . CRLF . | |
| "0123456789" . CRLF; | |
| last; | |
| } else { | |
| print $client | |
| "HTTP/1.1 404 Not Found" . CRLF . | |
| "X-Request: $rcount" . CRLF . | |
| "X-Connection: $ccount" . CRLF . | |
| "Connection: close" . CRLF . CRLF . | |
| "Oops, '$uri' not found" . CRLF; | |
| last; | |
| } | |
| } | |
| close $client; | |
| } | |
| } | |
| ############################################################################### |