use strict; use warnings; use Digest::MD5 qw(md5_hex); use Net::EmptyPort qw(empty_port check_port); use Test::More; use t::Util; plan skip_all => 'mruby support is off' unless server_features()->{mruby}; 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_hostport = "127.0.0.1:@{[empty_port()]}"; sub create_upstream { my @args = ( qw(plackup -s Starlet --keepalive-timeout 100 --access-log /dev/null --listen), $upstream_hostport, ASSETS_DIR . "/upstream.psgi", ); spawn_server( argv => \@args, is_ready => sub { $upstream_hostport =~ /:([0-9]+)$/s or die "failed to extract port number"; check_port($1); }, ); }; my $server = spawn_h2o(sub { my ($port, $tls_port) = @_; return << "EOT"; proxy.timeout.io: 1000 hosts: default: paths: /: mruby.handler: | Proc.new do |env| headers = {} env.each do |key, value| if /^HTTP_/.match(key) headers[\$'] = value end end headers["x-h2o-mruby"] = "1" http_request("http://$upstream_hostport#{env["PATH_INFO"]}#{env["QUERY_STRING"]}", { method: env["REQUEST_METHOD"], headers: headers, body: env["rack.input"], }).join end /as_str: mruby.handler: | Proc.new do |env| [200, {}, [http_request("http://$upstream_hostport/index.txt").join[2].join]] end /cl: mruby.handler: | Proc.new do |env| if !/^\\/([0-9]+)/.match(env["PATH_INFO"]) raise "failed to parse PATH_INFO" end cl = \$1 body = ["abc", "def", "ghi", "jkl", "mno"] if \$'.length != 0 class T def initialize(a) \@a = a end def each(&b) \@a.each(&b) end end body = T.new(body) end [200, {"content-length" => cl}, body] end /esi: mruby.handler: | class ESIResponse def initialize(input) \@parts = input.split /()/ \@parts.each_with_index do |part, index| if /^ sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/index.txt"); like $headers, qr{HTTP/[^ ]+ 500\s}is; }; my $upstream = create_upstream(); subtest "get" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/index.txt"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, "hello\n"; }; subtest "headers" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/echo-headers"); like $headers, qr{^HTTP/[^ ]+ 200\s}is; like $body, qr{^host: $upstream_hostport$}im; unlike $body, qr{^host: 127.0.0.1:$port$}im; like $body, qr{^user-agent: *curl/}im; like $body, qr{^accept: *\*/\*$}im; like $body, qr{^x-h2o-mruby:}im; }; subtest "post" => sub { my ($headers, $body) = run_prog("$curl_cmd --data 'hello world' $proto://127.0.0.1:$port/echo"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, 'hello world'; }; subtest "slow-chunked" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/streaming-body"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, (join "", 1..30); }; subtest "as_str" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/as_str/"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, "hello\n"; }; subtest "content-length" => sub { subtest "non-chunked" => sub { for my $i (0..15) { subtest "cl=$i" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/cl/$i"); like $headers, qr{^HTTP/[^ ]+ 200\s.*\ncontent-length:\s*$i\r}is; is $body, substr "abcdefghijklmno", 0, $i; } }; for my $i (16..30) { subtest "cl=$i" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/cl/$i"); like $headers, qr{^HTTP/[^ ]+ 200\s.*\ncontent-length:\s*15\r}is; is $body, "abcdefghijklmno"; } }; }; subtest "chunked" => sub { for my $i (0..30) { subtest "cl=$i" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/cl/$i/chunked"); like $headers, qr{^HTTP/[^ ]+ 200\s.*\ncontent-length:\s*$i\r}is; is $body, substr "abcdefghijklmno", 0, $i; } }; }; }; subtest "esi" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/esi/"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, "Hello to the world, from H2O!\n"; }; subtest "fast-path-partial" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/fast-path-partial/"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, join "", 2..30; }; subtest "async-delegate" => sub { subtest "non-delegated" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/async-delegate/index.txt"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, "hello\n"; }; subtest "delegated" => sub { my ($headers, $body) = run_prog("$curl_cmd $proto://127.0.0.1:$port/async-delegate/notfound"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, "delegated!"; }; }; }); subtest 'empty body' => sub { my $upstream = create_upstream(); my $server = spawn_h2o(sub { my ($port, $tls_port) = @_; return << "EOT"; hosts: default: paths: /no-content: mruby.handler: | proc {|env| resp = http_request("http://$upstream_hostport/no-content").join resp[2] = [resp[2].join] resp } /head: mruby.handler: | proc {|env| resp = http_request("http://$upstream_hostport/index.txt", { :method => 'HEAD' }).join resp[2] = [resp[2].join] resp } EOT }); run_with_curl($server, sub { my ($proto, $port, $curl_cmd) = @_; $curl_cmd .= ' --silent --dump-header /dev/stderr'; subtest "no content" => sub { my ($headers, $body) = run_prog("$curl_cmd -m 1 $proto://127.0.0.1:$port/no-content"); like $headers, qr{HTTP/[^ ]+ 204\s}is; is $body, ""; }; subtest "head" => sub { my ($headers, $body) = run_prog("$curl_cmd -m 1 $proto://127.0.0.1:$port/head"); like $headers, qr{HTTP/[^ ]+ 200\s}is; is $body, ""; }; }); }; done_testing();