#!/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;
	}
}

###############################################################################
