summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/t/50reverse-proxy
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-0.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-1.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-10.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-11.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-12.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-13.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-14.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-15.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-2.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-3.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-4.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-5.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-6.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-7.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-8.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-9.t4
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-added-headers.t73
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-config.t104
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-disconnected-keepalive.t77
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-drop-headers.t57
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-https.t70
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-preserve-case.t68
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-proxy-protocol.t55
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-session-resumption.t210
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy-upstream-down.t35
-rw-r--r--web/server/h2o/libh2o/t/50reverse-proxy/hello.txt1
-rwxr-xr-xweb/server/h2o/libh2o/t/50reverse-proxy/test.pl249
27 files changed, 1063 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-0.t b/web/server/h2o/libh2o/t/50reverse-proxy-0.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-0.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-1.t b/web/server/h2o/libh2o/t/50reverse-proxy-1.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-1.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-10.t b/web/server/h2o/libh2o/t/50reverse-proxy-10.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-10.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-11.t b/web/server/h2o/libh2o/t/50reverse-proxy-11.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-11.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-12.t b/web/server/h2o/libh2o/t/50reverse-proxy-12.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-12.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-13.t b/web/server/h2o/libh2o/t/50reverse-proxy-13.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-13.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-14.t b/web/server/h2o/libh2o/t/50reverse-proxy-14.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-14.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-15.t b/web/server/h2o/libh2o/t/50reverse-proxy-15.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-15.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-2.t b/web/server/h2o/libh2o/t/50reverse-proxy-2.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-2.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-3.t b/web/server/h2o/libh2o/t/50reverse-proxy-3.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-3.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-4.t b/web/server/h2o/libh2o/t/50reverse-proxy-4.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-4.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-5.t b/web/server/h2o/libh2o/t/50reverse-proxy-5.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-5.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-6.t b/web/server/h2o/libh2o/t/50reverse-proxy-6.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-6.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-7.t b/web/server/h2o/libh2o/t/50reverse-proxy-7.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-7.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-8.t b/web/server/h2o/libh2o/t/50reverse-proxy-8.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-8.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-9.t b/web/server/h2o/libh2o/t/50reverse-proxy-9.t
new file mode 100644
index 00000000..28e191ff
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-9.t
@@ -0,0 +1,4 @@
+$0 =~ /-([0-9]+)\.t$/s
+ or die "failed to extract mode";
+exec $^X, "t/50reverse-proxy/test.pl", "--mode=$1";
+die "failed to invoke $^X t/50reverse-proxy/test.pl:$!";
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-added-headers.t b/web/server/h2o/libh2o/t/50reverse-proxy-added-headers.t
new file mode 100644
index 00000000..0e87a198
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-added-headers.t
@@ -0,0 +1,73 @@
+use strict;
+use warnings;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+my $upstream_port = empty_port();
+
+my $guard = spawn_server(
+ argv => [ qw(plackup -s Starlet --keepalive-timeout 100 --access-log /dev/null --listen), $upstream_port, ASSETS_DIR . "/upstream.psgi" ],
+ is_ready => sub {
+ check_port($upstream_port);
+ },
+);
+
+sub do_test {
+ my $emit_xff = shift;
+ my $emit_via = shift;
+ my $emit_xff_str = $emit_xff ? "ON" : "OFF";
+ my $emit_via_str = $emit_via ? "ON" : "OFF";
+
+ my $server = spawn_h2o(<< "EOT");
+proxy.emit-x-forwarded-headers: $emit_xff_str
+proxy.emit-via-header: $emit_via_str
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1.XIP.IO:$upstream_port
+EOT
+
+ run_with_curl($server, sub {
+ my ($proto, $port, $curl) = @_;
+ my $resp = `$curl --silent $proto://127.0.0.1:$port/echo-headers`;
+ if ($emit_xff) {
+ like $resp, qr/^x-forwarded-for: ?127\.0\.0\.1$/mi, "x-forwarded-for";
+ like $resp, qr/^x-forwarded-proto: ?$proto$/mi, "x-forwarded-proto";
+ } else {
+ unlike $resp, qr/^x-forwarded-for: ?127\.0\.0\.1$/mi, "x-forwarded-for not present";
+ unlike $resp, qr/^x-forwarded-proto: ?$proto$/mi, "x-forwarded-proto not present";
+ }
+ if ($emit_via) {
+ like $resp, qr/^via: ?[^ ]+ 127\.0\.0\.1:$port$/mi, "via";
+ } else {
+ unlike $resp, qr/^via: ?[^ ]+ 127\.0\.0\.1:$port$/mi, "via";
+ }
+ $resp = `$curl --silent --header 'X-Forwarded-For: 127.0.0.2' --header 'Via: 2 example.com' $proto://127.0.0.1:$port/echo-headers`;
+ if ($emit_xff) {
+ like $resp, qr/^x-forwarded-for: ?127\.0\.0\.2, 127\.0\.0\.1$/mi, "x-forwarded-for (append)";
+ } else {
+ like $resp, qr/^x-forwarded-for: ?127\.0\.0\.2$/mi, "x-forwarded-for only contains the original header";
+ }
+ if ($emit_via) {
+ like $resp, qr/^via: ?2 example.com, [^ ]+ 127\.0\.0\.1:$port$/mi, "via (append)";
+ } else {
+ like $resp, qr/^via: 2 example.com$/mi, "via left as-is";
+ }
+ });
+}
+
+do_test(0, 0);
+do_test(0, 1);
+do_test(1, 0);
+do_test(1, 1);
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-config.t b/web/server/h2o/libh2o/t/50reverse-proxy-config.t
new file mode 100644
index 00000000..62b34668
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-config.t
@@ -0,0 +1,104 @@
+use strict;
+use warnings;
+use Digest::MD5 qw(md5_hex);
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+
+# start upstream
+my $upstream_port = empty_port();
+my $upstream = spawn_server(
+ argv => [
+ qw(plackup -MPlack::App::File -s Starlet --access-log /dev/null -p), $upstream_port,
+ ASSETS_DIR . "/upstream.psgi",
+ ],
+ is_ready => sub {
+ check_port($upstream_port);
+ },
+);
+
+subtest 'preserve-host' => sub {
+ my $doit = sub {
+ my $flag = shift;
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+ proxy.preserve-host: @{[ $flag ? 'ON' : 'OFF' ]}
+EOT
+ my $res = `curl --silent http://127.0.0.1:$server->{port}/echo-headers`;
+ like $res, qr/^host: 127.0.0.1:@{[ $flag ? $server->{port} : $upstream_port ]}$/im, 'host header';
+
+ $res = `curl --silent --dump-header /dev/stdout "http://127.0.0.1:$server->{port}/?resp:status=302&resp:location=http://127.0.0.1:$server->{port}/foo"`;
+ like $res, qr{^location: http://127\.0\.0\.1:$server->{port}/foo}im, 'location: :server_port';
+ warn qq{curl --silent --dump-header /dev/stdout "http://127.0.0.1:$server->{port}/?resp:status=302&resp:location=http://127.0.0.1:$upstream_port/foo"};
+ $res = `curl --silent --dump-header /dev/stdout "http://127.0.0.1:$server->{port}/?resp:status=302&resp:location=http://127.0.0.1:$upstream_port/foo"`;
+ like $res, qr{^location: http://127\.0\.0\.1:$server->{port}/foo}im, 'location :upstream_port';
+ };
+
+ subtest 'ON' => sub {
+ $doit->(1);
+ };
+ subtest 'OFF' => sub {
+ $doit->(0);
+ };
+};
+
+subtest 'timeout.io' => sub {
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+ proxy.timeout.io: 2000
+EOT
+ my $fetch = sub {
+ my $sleep = shift;
+ `curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/sleep-and-respond?sleep=$sleep 2>&1 > /dev/null`;
+ };
+ my $resp = $fetch->(1);
+ like $resp, qr{^HTTP/1\.1 200 }s, "respond before timeout";
+ $resp = $fetch->(3);
+ like $resp, qr{^HTTP/1\.1 502 }s, "respond after timeout";
+};
+
+subtest 'infinite-internal-redirect' => sub {
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+reproxy: ON
+EOT
+ my $resp = `curl --silent --dump-header /dev/stderr "http://127.0.0.1:$server->{port}/?resp:x-reproxy-url=http://127.0.0.1:$upstream_port/infinite-redirect" 2>&1 > /dev/null`;
+ like $resp, qr{^HTTP/1\.1 502 }s;
+};
+
+subtest 'max-delegations' => sub {
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+reproxy: ON
+max-delegations: 0
+EOT
+ my $resp = `curl --silent --dump-header /dev/stderr "http://127.0.0.1:$server->{port}/?resp:x-reproxy-url=http://127.0.0.1:$upstream_port/index.txt" 2>&1 > /dev/null`;
+ like $resp, qr{^HTTP/1\.1 502 }s;
+};
+
+done_testing;
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-disconnected-keepalive.t b/web/server/h2o/libh2o/t/50reverse-proxy-disconnected-keepalive.t
new file mode 100644
index 00000000..4e35cba9
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-disconnected-keepalive.t
@@ -0,0 +1,77 @@
+use strict;
+use warnings;
+use File::Temp qw(tempfile);
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+subtest "tcp" => sub {
+ my $port = empty_port();
+ my $upstream = spawn_upstream($port);
+ doit("127.0.0.1:$port");
+};
+
+subtest "unix-socket" => sub {
+ plan skip_all => 'skipping unix-socket tests, requires Starlet >= 0.25'
+ if `perl -MStarlet -e 'print \$Starlet::VERSION'` < 0.25;
+
+ (undef, my $sockfn) = tempfile(UNLINK => 0);
+ unlink $sockfn;
+ my $guard = Scope::Guard->new(sub {
+ unlink $sockfn;
+ });
+
+ my $upstream = spawn_upstream($sockfn);
+ doit("[unix:$sockfn]");
+};
+
+done_testing;
+
+sub doit {
+ my $upaddr = shift;
+
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://$upaddr
+ proxy.timeout.io: 1000
+ proxy.timeout.keepalive: 10000
+EOT
+ my $port = $server->{port};
+
+ my $check_req = sub {
+ my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$port/index.txt 2>&1");
+ like $headers, qr{^HTTP/1\.1 200 }is;
+ is $body, "hello\n";
+ };
+ subtest "first-connect" => $check_req;
+ subtest "redo-immediate" => $check_req;
+ sleep 2;
+ subtest "redo-after-upstream-disconnect" => $check_req;
+ sleep 2;
+ subtest "once-more" => $check_req;
+};
+
+sub spawn_upstream {
+ my $addr = shift;
+ spawn_server(
+ argv => [
+ qw(plackup -s Starlet --max-keepalive-reqs 100 --keepalive-timeout 1 --access-log /dev/null --listen), $addr,
+ ASSETS_DIR . "/upstream.psgi"
+ ],
+ is_ready => sub {
+ if ($addr =~ /^\d+$/) {
+ check_port($addr);
+ } else {
+ !! -e $addr;
+ }
+ },
+ );
+}
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-drop-headers.t b/web/server/h2o/libh2o/t/50reverse-proxy-drop-headers.t
new file mode 100644
index 00000000..0ae1e61d
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-drop-headers.t
@@ -0,0 +1,57 @@
+use strict;
+use warnings;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+my $upstream_port = empty_port();
+
+my $guard = spawn_server(
+ argv => [ qw(plackup -s Starlet --keepalive-timeout 100 --access-log /dev/null --listen), "127.0.0.1:$upstream_port", ASSETS_DIR . "/upstream.psgi" ],
+ is_ready => sub {
+ check_port($upstream_port);
+ },
+);
+
+subtest 'request-header' => sub {
+# proxy-authenticate
+
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1.XIP.IO:$upstream_port
+EOT
+
+ my $curl = 'curl --silent --dump-header /dev/stderr';
+ my ($headers, $body) = run_prog("$curl"
+ . " -H 'Proxy-Authenticate: hoge'"
+ . " -H 'Date: Thu, 01 Jan 1970 00:00:00 GMT'"
+ . " http://127.0.0.1:@{[$server->{port}]}/echo-headers");
+ unlike $body, qr/^proxy-authenticate:/mi, 'proxy-authenticate header should be dropped';
+ like $body, qr/^date:/mi, 'date request header is not dropped';
+};
+
+subtest 'response header' => sub {
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1.XIP.IO:$upstream_port
+EOT
+
+ my $curl = 'curl --silent --dump-header /dev/stderr';
+ my ($headers, $body) = run_prog("$curl http://127.0.0.1:@{[$server->{port}]}/fixed-date-header");
+ unlike $headers, qr/Thu, 01 Jan 1970 00:00:00 GMT/, "date response header from upstream should be dropped and replaced";
+};
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-https.t b/web/server/h2o/libh2o/t/50reverse-proxy-https.t
new file mode 100644
index 00000000..1bb499f9
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-https.t
@@ -0,0 +1,70 @@
+use strict;
+use warnings;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+
+my $upstream_port = empty_port();
+my $upstream = spawn_server(
+ argv => [
+ qw(
+ plackup -s Standalone --ssl=1 --ssl-key-file=examples/h2o/server.key --ssl-cert-file=examples/h2o/server.crt --port
+ ),
+ $upstream_port, ASSETS_DIR . "/upstream.psgi"
+ ],
+ is_ready => sub {
+ check_port($upstream_port);
+ },
+);
+
+subtest "reverse-proxy" => sub {
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ "/verify":
+ proxy.reverse.url: https://127.0.0.1:$upstream_port
+ "/no-verify":
+ proxy.reverse.url: https://127.0.0.1:$upstream_port
+ proxy.ssl.verify-peer: OFF
+ "/wikipedia":
+ proxy.reverse.url: https://en.wikipedia.org/wiki/Main_Page
+EOT
+ run_with_curl($server, sub {
+ my ($proto, $port, $curl) = @_;
+ my $resp = `$curl --silent --dump-header /dev/stderr --max-redirs 0 $proto://127.0.0.1:$port/verify/ 2>&1 > /dev/null`;
+ like $resp, qr{^HTTP/[^ ]* 502\s}is;
+ $resp = `$curl --silent --dump-header /dev/stderr --max-redirs 0 $proto://127.0.0.1:$port/no-verify/ 2>&1 > /dev/null`;
+ unlike $resp, qr{^HTTP/[^ ]* 502\s}is;
+ $resp = `$curl --silent --dump-header /dev/stderr --max-redirs 0 $proto://127.0.0.1:$port/wikipedia/ 2>&1 > /dev/null`;
+ like $resp, qr{^HTTP/[^ ]* 200\s}is;
+ });
+};
+
+subtest "reproxy" => sub {
+ plan skip_all => "mruby support is off"
+ unless server_features()->{mruby};
+ my $server = spawn_h2o(<< "EOT");
+proxy.ssl.verify-peer: OFF
+hosts:
+ default:
+ paths:
+ "/":
+ reproxy: ON
+ mruby.handler: |
+ Proc.new do |env|
+ [200, {"x-reproxy-url" => "https://127.0.0.1:$upstream_port/index.txt"}, ["should never see this"]]
+ end
+EOT
+ run_with_curl($server, sub {
+ my ($proto, $port, $curl) = @_;
+ my $resp = `$curl --silent --dump-header /dev/stdout --max-redirs 0 $proto://127.0.0.1:$port/`;
+ like $resp, qr{^HTTP/[^ ]* 200}im;
+ like $resp, qr{^hello$}m;
+ });
+};
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-preserve-case.t b/web/server/h2o/libh2o/t/50reverse-proxy-preserve-case.t
new file mode 100644
index 00000000..da2aee2c
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-preserve-case.t
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+my $upstream_port = empty_port();
+
+my $upstream = new IO::Socket::INET (
+ LocalHost => '127.0.0.1',
+ LocalPort => $upstream_port,
+ Proto => 'tcp',
+ Listen => 1,
+ Reuse => 1
+);
+die "cannot create socket $!\n" unless $upstream;
+
+sub handler_curl {
+ my $socket = shift;
+ my $client_socket = $socket->accept();
+
+ my $data = "";
+ $client_socket->recv($data, 4906);
+
+ my $resp = "HTTP/1.0 200 Ok\r\nMyResponseHeader:1\r\nContent-Length:2\r\nConnection: close\r\n\r\nOk";
+ $client_socket->send($resp);
+ $client_socket->flush;
+
+ close($client_socket);
+ return $data;
+};
+
+
+my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+EOT
+
+run_with_curl($server, sub {
+ my ($proto, $port, $curl) = @_;
+ open(CURL, "$curl -HUpper-Case:TheValue -kv $proto://127.0.0.1:$port/ 2>&1 |");
+ my $forwarded = handler_curl($upstream);
+ my @lines;
+ while (<CURL>) {
+ push(@lines, $_);
+
+ }
+ my $resp = join("", @lines);
+ if ($curl =~ /http2/) {
+ like($forwarded, qr{upper-case:\s*TheValue}, "Request header name is lowercased");
+ like($resp, qr{myresponseheader:\s*1}, "Response header name is lowercase");
+ } else {
+ like($forwarded, qr{Upper-Case:\s*TheValue}, "Request header name is not lowercased");
+ like($resp, qr{MyResponseHeader:\s*1}, "Response header name has case preserved");
+ }
+ });
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-proxy-protocol.t b/web/server/h2o/libh2o/t/50reverse-proxy-proxy-protocol.t
new file mode 100644
index 00000000..0bb1a821
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-proxy-protocol.t
@@ -0,0 +1,55 @@
+use strict;
+use warnings;
+use IO::Socket::INET;
+use Test::TCP;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use Scope::Guard qw(guard);
+use t::Util;
+
+my $upstream_port = empty_port();
+
+my $listen = IO::Socket::INET->new(
+ LocalAddr => '127.0.0.1',
+ LocalPort => $upstream_port,
+ Listen => 5,
+) or die "failed to listen to 127.0.0.1:$upstream_port:$!";
+
+my $upstream_guard = do {
+ my $pid = fork;
+ die "fork failed:$!"
+ unless defined $pid;
+ if ($pid == 0) {
+ # server process
+ while (1) {
+ if (my $conn = $listen->accept) {
+ sysread $conn, my $buf, 4096;
+print STDERR "**** $buf";
+ syswrite $conn, "HTTP/1.1 200 OK\r\nconnection: close\r\n\r\n$buf";
+print STDERR "**** yeoh";
+ $conn->close;
+ }
+ }
+ }
+ guard {
+ kill 'TERM', $pid;
+ };
+};
+
+my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ "/":
+ proxy.reverse.url: http://127.0.0.1:$upstream_port/
+ proxy.proxy-protocol: ON
+ proxy.timeout.keepalive: 0
+EOT
+
+run_with_curl($server, sub {
+ my ($proto, $port, $curl_cmd) = @_;
+ my $resp = `$curl_cmd --silent $proto://127.0.0.1:$port/hello`;
+ like $resp, qr{^PROXY TCP4 127\.0\.0\.1 127\.0\.0\.1 [0-9]{1,5} $port\r\nGET /hello HTTP/1\.}is;
+});
+
+done_testing;
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-session-resumption.t b/web/server/h2o/libh2o/t/50reverse-proxy-session-resumption.t
new file mode 100644
index 00000000..acf6a781
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-session-resumption.t
@@ -0,0 +1,210 @@
+use strict;
+use warnings;
+use File::Temp qw(tempdir);
+use Net::EmptyPort qw(check_port empty_port);
+use Test::Builder;
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+
+my $tempdir = tempdir(CLEANUP => 1);
+
+sub create_upstream {
+ unlink "$tempdir/access_log";
+ return spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ file.dir: @{[ DOC_ROOT ]}
+ access-log:
+ path: $tempdir/access_log
+ format: "%{ssl.session-reused}x"
+EOT
+}
+
+sub doit {
+ my ($conf, $scenario, $opts) = @_;
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ $opts ||= +{};
+
+ my $upstream = $opts->{upstream} || create_upstream();
+ if (ref($conf) eq 'CODE') {
+ $conf = $conf->($upstream);
+ }
+ my $server = spawn_h2o($conf);
+ my $port = $server->{port};
+
+ for my $s (@$scenario) {
+ my $path = $s->{path} || '/';
+ Time::HiRes::sleep($s->{interval}) if $s->{interval};
+ my $res = `curl --silent --dump-header /dev/stderr http://127.0.0.1:@{[$server->{port}]}$path 2>&1 > /dev/null`;
+ like $res, qr{^HTTP/1\.1 200 }, $s->{desc};
+ }
+
+ my @log = do {
+ open my $fh, "<", "$tempdir/access_log"
+ or die "failed to open access_log:$!";
+ map { my $l = $_; chomp $l; $l } <$fh>;
+ };
+
+ for my $i (0..scalar(@$scenario)-1) {
+ my $s = $scenario->[$i];
+ next unless defined($s->{expected});
+
+ my $reused = $log[$i] + 0;
+ is($reused, $s->{expected}, $s->{desc});
+ }
+};
+
+subtest 'default' => sub {
+ doit(sub {
+ my ($upstream) = @_;
+ return <<"EOC";
+proxy.ssl.verify-peer: OFF
+proxy.timeout.keepalive: 0
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+EOC
+ }, [
+ +{},
+ +{ expected => 1 },
+ ]);
+};
+
+subtest 'off' => sub {
+ doit(sub {
+ my ($upstream) = @_;
+ return <<"EOC";
+proxy.timeout.keepalive: 0
+proxy.ssl.session-cache: OFF
+hosts:
+ default:
+ paths:
+ /:
+ proxy.ssl.verify-peer: OFF
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+EOC
+ }, [
+ +{},
+ +{ expected => 0 },
+ ]);
+};
+
+subtest 'lifetime' => sub {
+ doit(sub {
+ my ($upstream) = @_;
+ return <<"EOC";
+proxy.ssl.verify-peer: OFF
+proxy.timeout.keepalive: 0
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+ proxy.ssl.session-cache:
+ capacity: 4096
+ lifetime: 2
+EOC
+ }, [
+ +{},
+ +{ interval => 1, expected => 1 },
+ +{ interval => 2, expected => 0, desc => 'expire' },
+ ]);
+};
+
+subtest 'config' => sub {
+ doit(sub {
+ my ($upstream) = @_;
+ return <<"EOC";
+proxy.ssl.verify-peer: OFF
+proxy.timeout.keepalive: 0
+hosts:
+ default:
+ proxy.ssl.session-cache: OFF
+ paths:
+ /:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+ proxy.ssl.session-cache: ON
+ /sample.txt:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+ proxy.ssl.session-cache:
+ capacity: 4096
+ lifetime: 2
+
+EOC
+ }, [
+ +{ path => '/' },
+ +{ path => '/', interval => 0, expected => 1, desc => 'reuse on second request to /' },
+ +{ path => '/sample.txt', interval => 0, expected => 0, desc => 'not reuse on first request to /sample.txt' },
+ +{ path => '/sample.txt', interval => 1, expected => 1, desc => 'reuse on second request to /sample.txt' },
+ +{ path => '/sample.txt', interval => 2, expected => 0, desc => 'expire on third request to /sample.txt' },
+ ]);
+};
+
+subtest 'reproxy' => sub {
+ my $upstream = create_upstream();
+ my $upstream_port = $upstream->{tls_port};
+ my $app_port = empty_port();
+ my $app_server = spawn_server(
+ argv => [
+ qw(
+ plackup -s Starlet --max-workers=1 --port
+ ),
+ $app_port, "-e sub { [200, ['X-Reproxy-URL' => 'https://127.0.0.1:$upstream_port/'], []] }"
+ ],
+ is_ready => sub {
+ check_port($app_port);
+ },
+ );
+
+ doit(sub {
+ return <<"EOC";
+proxy.ssl.verify-peer: OFF
+proxy.timeout.keepalive: 0
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$app_port
+ reproxy: ON
+EOC
+ }, [
+ +{},
+ +{ expected => 1 },
+ ], +{ upstream => $upstream });
+};
+
+subtest 'multiple-hosts' => sub {
+ doit(sub {
+ my ($upstream) = @_;
+ return <<"EOC";
+proxy.ssl.verify-peer: OFF
+proxy.timeout.keepalive: 0
+proxy.ssl.session-cache: ON
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+ example.com:
+ paths:
+ /:
+ proxy.reverse.url: https://127.0.0.1:@{[$upstream->{tls_port}]}
+ proxy.ssl.verify-peer: ON
+ proxy.ssl.session-cache: OFF
+EOC
+ }, [
+ +{},
+ +{ expected => 1 },
+ ]);
+};
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy-upstream-down.t b/web/server/h2o/libh2o/t/50reverse-proxy-upstream-down.t
new file mode 100644
index 00000000..3f30a447
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy-upstream-down.t
@@ -0,0 +1,35 @@
+use strict;
+use warnings;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use t::Util;
+
+plan skip_all => 'curl not found'
+ unless prog_exists('curl');
+
+sub doit {
+ my $persistent = shift;
+ my $upstream_port = empty_port();
+ my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://127.0.0.1:$upstream_port
+ proxy.timeout.io: 2000
+@{[ $persistent ? "" : "proxy.timeout.keepalive: 0" ]}
+EOT
+ my $port = $server->{port};
+ my $res = `curl --max-time 5 --silent --dump-header /dev/stderr http://127.0.0.1:$port/ 2>&1 > /dev/null`;
+ like $res, qr{^HTTP/1\.1 502 }, "502 response on upstream error";
+};
+
+subtest 'non-persistent' => sub {
+ doit(0);
+};
+
+subtest 'persistent' => sub {
+ doit(1);
+};
+
+done_testing();
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy/hello.txt b/web/server/h2o/libh2o/t/50reverse-proxy/hello.txt
new file mode 100644
index 00000000..ce013625
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy/hello.txt
@@ -0,0 +1 @@
+hello
diff --git a/web/server/h2o/libh2o/t/50reverse-proxy/test.pl b/web/server/h2o/libh2o/t/50reverse-proxy/test.pl
new file mode 100755
index 00000000..3781cba3
--- /dev/null
+++ b/web/server/h2o/libh2o/t/50reverse-proxy/test.pl
@@ -0,0 +1,249 @@
+use strict;
+use warnings;
+use Digest::MD5 qw(md5_hex);
+use File::Temp qw(tempfile);
+use Getopt::Long;
+use Net::EmptyPort qw(check_port empty_port);
+use Test::More;
+use URI::Escape;
+use t::Util;
+
+my ($aggregated_mode, $h2o_keepalive, $starlet_keepalive, $starlet_force_chunked, $unix_socket);
+
+GetOptions(
+ "mode=i" => sub {
+ (undef, my $m) = @_;
+ $h2o_keepalive = ($m & 1) != 0;
+ $starlet_keepalive = ($m & 2) != 0;
+ $starlet_force_chunked = ($m & 4) != 0;
+ $unix_socket = ($m & 8) != 0;
+ },
+ "h2o-keepalive=i" => \$h2o_keepalive,
+ "starlet-keepalive=i" => \$starlet_keepalive,
+ "starlet-force-chunked=i" => \$starlet_force_chunked,
+ "unix-socket=i" => \$unix_socket,
+) or exit(1);
+
+plan skip_all => 'plackup not found'
+ unless prog_exists('plackup');
+
+plan skip_all => 'Starlet not found'
+ unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0;
+plan skip_all => 'skipping unix-socket tests, requires Starlet >= 0.25'
+ if $unix_socket && `perl -MStarlet -e 'print \$Starlet::VERSION'` < 0.25;
+
+my %files = map { do {
+ my $fn = DOC_ROOT . "/$_";
+ +($_ => { size => (stat $fn)[7], md5 => md5_file($fn) });
+} } qw(index.txt halfdome.jpg);
+
+my $huge_file_size = 50 * 1024 * 1024; # should be larger than the mmap_backend threshold of h2o
+my $huge_file = create_data_file($huge_file_size);
+my $huge_file_md5 = md5_file($huge_file);
+
+my ($unix_socket_file, $unix_socket_guard) = do {
+ (undef, my $fn) = tempfile(UNLINK => 0);
+ unlink $fn;
+ +(
+ $fn,
+ Scope::Guard->new(sub {
+ unlink $fn;
+ }),
+ );
+} if $unix_socket;
+
+my $upstream = $unix_socket_file ? "[unix:$unix_socket_file]" : "127.0.0.1:@{[empty_port()]}";
+
+my $guard = do {
+ local $ENV{FORCE_CHUNKED} = $starlet_force_chunked;
+ my @args = (qw(plackup -s Starlet --keepalive-timeout 100 --access-log /dev/null --listen), $unix_socket_file || $upstream);
+ if ($starlet_keepalive) {
+ push @args, "--max-keepalive-reqs=100";
+ }
+ push @args, ASSETS_DIR . "/upstream.psgi";
+ spawn_server(
+ argv => \@args,
+ is_ready => sub {
+ if ($unix_socket_file) {
+ !! -e $unix_socket_file;
+ } else {
+ $upstream =~ /:([0-9]+)$/s
+ or die "failed to extract port number";
+ check_port($1);
+ }
+ },
+ );
+};
+
+my $server = spawn_h2o(<< "EOT");
+hosts:
+ default:
+ paths:
+ /:
+ proxy.reverse.url: http://$upstream
+ /gzip:
+ proxy.reverse.url: http://$upstream
+ gzip: ON
+ /files:
+ file.dir: @{[ DOC_ROOT ]}
+@{[ $h2o_keepalive ? "" : " proxy.timeout.keepalive: 0" ]}
+reproxy: ON
+EOT
+
+run_with_curl($server, sub {
+ my ($proto, $port, $curl) = @_;
+ for my $file (sort keys %files) {
+ my $content = `$curl --silent --show-error $proto://127.0.0.1:$port/$file`;
+ is length($content), $files{$file}->{size}, "$proto://127.0.0.1/$file (size)";
+ is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/$file (md5)";
+ }
+ for my $file (sort keys %files) {
+ my $content = `$curl --silent --show-error --data-binary \@@{[ DOC_ROOT ]}/$file $proto://127.0.0.1:$port/echo`;
+ is length($content), $files{$file}->{size}, "$proto://127.0.0.1/echo (POST, $file, size)";
+ is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/echo (POST, $file, md5)";
+ }
+ if ($curl !~ /--http2/) {
+ for my $file (sort keys %files) {
+ my $content = `$curl --silent --show-error --header 'Transfer-Encoding: chunked' --data-binary \@@{[ DOC_ROOT ]}/$file $proto://127.0.0.1:$port/echo`;
+ is length($content), $files{$file}->{size}, "$proto://127.0.0.1/echo (POST, chunked, $file, size)";
+ is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/echo (POST, chunked, $file, md5)";
+ }
+ }
+ my $content = `$curl --silent --show-error --data-binary \@$huge_file $proto://127.0.0.1:$port/echo`;
+ is length($content), $huge_file_size, "$proto://127.0.0.1/echo (POST, mmap-backed, size)";
+ is md5_hex($content), $huge_file_md5, "$proto://127.0.0.1/echo (POST, mmap-backed, md5)";
+ if ($curl !~ /--http2/) {
+ $content = `$curl --silent --show-error --header 'Transfer-Encoding: chunked' --data-binary \@$huge_file $proto://127.0.0.1:$port/echo`;
+ is length($content), $huge_file_size, "$proto://127.0.0.1/echo (POST, chunked, mmap-backed, size)";
+ is md5_hex($content), $huge_file_md5, "$proto://127.0.0.1/echo (POST, chunked, mmap-backed, md5)";
+ }
+ subtest 'rewrite-redirect' => sub {
+ $content = `$curl --silent --dump-header /dev/stdout --max-redirs 0 "$proto://127.0.0.1:$port/?resp:status=302&resp:location=http://@{[uri_escape($upstream)]}/abc"`;
+ like $content, qr{HTTP/[^ ]+ 302\s}m;
+ like $content, qr{^location: ?$proto://127.0.0.1:$port/abc\r$}m;
+ };
+ subtest "x-reproxy-url ($proto)" => sub {
+ my $fetch_test = sub {
+ my $url_prefix = shift;
+ for my $file (sort keys %files) {
+ my $content = `$curl --silent --show-error "$proto://127.0.0.1:$port/404?resp:status=200&resp:x-reproxy-url=$url_prefix$file"`;
+ is length($content), $files{$file}->{size}, "$file (size)";
+ is md5_hex($content), $files{$file}->{md5}, "$file (md5)";
+ }
+ };
+ subtest "abs-url" => sub {
+ $fetch_test->("http://@{[uri_escape($upstream)]}/");
+ };
+ subtest "abs-path" => sub {
+ $fetch_test->("/");
+ };
+ subtest "rel-path" => sub {
+ $fetch_test->("");
+ };
+ my $content = `$curl --silent --show-error "$proto://127.0.0.1:$port/streaming-body?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/index.txt"`;
+ is $content, "hello\n", "streaming-body";
+ $content = `$curl --silent "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=https://default/files/index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "to file handler (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "to file handler (md5)";
+ $content = `$curl --silent "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/?resp:status=302%26resp:location=index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "reproxy & internal redirect to upstream (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "reproxy & internal redirect to upstream (md5)";
+ $content = `$curl --silent "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/?resp:status=302%26resp:location=https://default/files/index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "reproxy & internal redirect to file (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "reproxy & internal redirect to file (md5)";
+ $content = `$curl --silent "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://default/files"`;
+ is length($content), $files{"index.txt"}->{size}, "redirect handled internally after delegation (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "redirect handled internally after delegation (md5)";
+ };
+ subtest "x-forwarded ($proto)" => sub {
+ my $resp = `$curl --silent $proto://127.0.0.1:$port/echo-headers`;
+ like $resp, qr/^x-forwarded-for: ?127\.0\.0\.1$/mi, "x-forwarded-for";
+ like $resp, qr/^x-forwarded-proto: ?$proto$/mi, "x-forwarded-proto";
+ like $resp, qr/^via: ?[^ ]+ 127\.0\.0\.1:$port$/mi, "via";
+ $resp = `$curl --silent --header 'X-Forwarded-For: 127.0.0.2' --header 'Via: 2 example.com' $proto://127.0.0.1:$port/echo-headers`;
+ like $resp, qr/^x-forwarded-for: ?127\.0\.0\.2, 127\.0\.0\.1$/mi, "x-forwarded-for (append)";
+ like $resp, qr/^via: ?2 example.com, [^ ]+ 127\.0\.0\.1:$port$/mi, "via (append)";
+ };
+ subtest 'issues/266' => sub {
+ my $resp = `$curl --dump-header /dev/stderr --silent -H 'cookie: a=@{['x' x 4000]}' $proto://127.0.0.1:$port/index.txt 2>&1 > /dev/null`;
+ like $resp, qr{^HTTP/[^ ]+ 200\s}m;
+ };
+ subtest 'gzip' => sub {
+ plan skip_all => 'curl issue #661'
+ if $curl =~ /--http2/;
+ my $resp = `$curl --silent -H Accept-Encoding:gzip $proto://127.0.0.1:$port/gzip/alice.txt | gzip -cd`;
+ is md5_hex($resp), md5_file("@{[DOC_ROOT]}/alice.txt");
+ };
+});
+
+subtest 'nghttp' => sub {
+ plan skip_all => 'nghttp not found'
+ unless prog_exists('nghttp');
+ my $doit = sub {
+ my ($proto, $opt, $port) = @_;
+ for my $file (sort keys %files) {
+ my $content = `nghttp $opt $proto://127.0.0.1:$port/$file`;
+ is length($content), $files{$file}->{size}, "$proto://127.0.0.1/$file (size)";
+ is md5_hex($content), $files{$file}->{md5}, "$proto://127.0.0.1/$file (md5)";
+ }
+ my $out = `nghttp $opt -d t/50reverse-proxy/hello.txt $proto://127.0.0.1:$port/echo`;
+ is $out, "hello\n", "$proto://127.0.0.1/echo (POST)";
+ $out = `nghttp $opt -m 10 $proto://127.0.0.1:$port/index.txt`;
+ is $out, "hello\n" x 10, "$proto://127.0.0.1/index.txt x 10 times";
+ $out = `nghttp $opt -d $huge_file $proto://127.0.0.1:$port/echo`;
+ is length($out), $huge_file_size, "$proto://127.0.0.1/echo (mmap-backed, size)";
+ is md5_hex($out), $huge_file_md5, "$proto://127.0.0.1/echo (mmap-backed, md5)";
+ subtest 'cookies' => sub {
+ plan skip_all => 'nghttp issues #161'
+ if $opt eq '-u';
+ $out = `nghttp $opt -H 'cookie: a=b' -H 'cookie: c=d' $proto://127.0.0.1:$port/echo-headers`;
+ like $out, qr{^cookie: a=b; c=d$}m;
+ };
+ subtest "x-reproxy-url ($proto)" => sub {
+ for my $file (sort keys %files) {
+ my $content = `nghttp $opt "$proto://127.0.0.1:$port/404?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/$file"`;
+ is length($content), $files{$file}->{size}, "$file (size)";
+ is md5_hex($content), $files{$file}->{md5}, "$file (md5)";
+ }
+ my $content = `nghttp $opt "$proto://127.0.0.1:$port/streaming-body?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/index.txt"`;
+ is $content, "hello\n", "streaming-body";
+ $content = `nghttp $opt "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=https://default/files/index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "to file handler (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "to file handler (md5)";
+ $content = `nghttp $opt "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/?resp:status=302%26resp:location=index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "reproxy & internal redirect to upstream (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "reproxy & internal redirect to upstream (md5)";
+ $content = `nghttp $opt "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://@{[uri_escape($upstream)]}/?resp:status=302%26resp:location=https://default/files/index.txt"`;
+ is length($content), $files{"index.txt"}->{size}, "reproxy & internal redirect to file (size)";
+ is md5_hex($content), $files{"index.txt"}->{md5}, "reproxy & internal redirect to file (md5)";
+ $content = `nghttp -v $opt "$proto://127.0.0.1:$port/?resp:status=200&resp:x-reproxy-url=http://default/files"`;
+ unlike $content, qr/ :status: 3/, "once delegated, redirects of the file handler should be handled internally";
+ };
+ subtest 'issues/185' => sub {
+ my $out = `nghttp $opt -v "$proto://127.0.0.1:$port/?resp:access-control-allow-origin=%2a"`;
+ is $?, 0;
+ like $out, qr/ access-control-allow-origin: \*$/m;
+ };
+ subtest 'issues/192' => sub {
+ my $cookie = '_yohoushi_session=ZU5tK2FhcllVQ1RGaTZmZE9MUXozZnAzdTdmR250ZjRFU1hNSnZ4Y2JxZm9pYzJJSEpISGFKNmtWcW1HcjBySmUxZzIwNngrdlVIOC9jdmg0R3I3TFR4eVYzaHlFSHdEL1M4dnh1SmRCbVl3ZE5FckZEU1NyRmZveWZwTmVjcVV5V1JhNUd5REIvWjAwQ3RiT1ZBNGVMUkhiN0tWR0c1RzZkSVhrVkdoeW1lWXRDeHJPUW52NUwxSytRTEQrWXdoZ1EvVG9kak9aeUxnUFRNTDB4Vis1RDNUYWVHZm12bDgwL1lTa09MTlJYcGpXN2VUWmdHQ2FuMnVEVDd4c3l1TTJPMXF1dGhYcGRHS2U2bW9UaG0yZGIwQ0ZWVzFsY1hNTkY5cVFvWjNqSWNrQ0pOY1gvMys4UmRHdXJLU1A0ZTZQc3pSK1dKcXlpTEF2djJHLzUwbytwSnVpS0xhdFp6NU9kTDhtcmgxamVXMkI0Nm9Nck1rMStLUmV0TEdUeGFSTjlKSzM0STc3NTlSZ05ZVjJhWUNibkdzY1I1NUg4bG96dWZSeGorYzF4M2tzMGhKSkxmeFBTNkpZS09HTFgrREN4SWd4a29kamRxT3FobDRQZ2xMVUE9PS0tQUxSWU5nWmVTVzRoN09sS3pmUVM3dz09--3a411c0cf59845f0b8ccf61f69b8eb87aa1727ac; path=/; HttpOnly';
+ my $cookie_encoded = $cookie;
+ $cookie_encoded =~ s{([^A-Za-z0-9_])}{sprintf "%%%02x", ord $1}eg;
+ $out = `nghttp $opt -v $proto://127.0.0.1:$port/?resp:set-cookie=$cookie_encoded`;
+ is $?, 0;
+ like $out, qr/ set-cookie: $cookie$/m;
+ };
+ };
+ subtest 'http (upgrade)' => sub {
+ $doit->('http', '-u', $server->{port});
+ };
+ subtest 'http (direct)' => sub {
+ $doit->('http', '', $server->{port});
+ };
+ subtest 'https' => sub {
+ plan skip_all => 'OpenSSL does not support protocol negotiation; it is too old'
+ unless openssl_can_negotiate();
+ $doit->('https', '', $server->{tls_port});
+ };
+};
+
+done_testing;