diff options
Diffstat (limited to '')
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby-acl.t | 39 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby-dos-detector.t | 111 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby-htpasswd.t | 48 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby-http-request.t | 269 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby.t | 541 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby/hello.rb | 3 | ||||
-rw-r--r-- | web/server/h2o/libh2o/t/50mruby/index.html | 1 |
7 files changed, 1012 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/t/50mruby-acl.t b/web/server/h2o/libh2o/t/50mruby-acl.t new file mode 100644 index 00000000..0c84a682 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby-acl.t @@ -0,0 +1,39 @@ +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); +use Test::More; +use Test::Exception; +use t::Util; + +plan skip_all => 'mruby support is off' + unless server_features()->{mruby}; + +subtest "invalid configuration 1" => sub { + throws_ok sub { + spawn_h2o(<< 'EOT'); +hosts: + default: + paths: + /: + mruby.handler: | + acl { respond(403) } + acl { respond(200) } +EOT + }, qr/server failed to start/, 'acl cannot be called more than once'; +}; + +subtest "invalid configuration 2" => sub { + throws_ok sub { + spawn_h2o(<< 'EOT'); +hosts: + default: + paths: + /: + mruby.handler: | + acl { respond(403) } + proc {|env| [200, {}, []]} +EOT + }, qr/server failed to start/, 'acl configuration is ignored'; +}; + +done_testing(); diff --git a/web/server/h2o/libh2o/t/50mruby-dos-detector.t b/web/server/h2o/libh2o/t/50mruby-dos-detector.t new file mode 100644 index 00000000..6b7cdc56 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby-dos-detector.t @@ -0,0 +1,111 @@ +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); +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'); + +subtest "default callback" => sub { + my $server = spawn_h2o(<< 'EOT'); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + require "share/h2o/mruby/dos_detector.rb" + DoSDetector.new({ + :strategy => DoSDetector::CountingStrategy.new({ :period => 1000, :threshold => 2, :ban_period => 1 << 32 }), + }) + mruby.handler: + Proc.new do |env| + [200, {}, []] + end +EOT + subtest "forbidden" => sub { + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }s, "status"; + } + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 403 }s, "status"; + is $body, "Forbidden", "content"; + } + }; +}; + +subtest "fallthrough callback" => sub { + my $server = spawn_h2o(<< 'EOT'); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + require "share/h2o/mruby/dos_detector.rb" + DoSDetector.new({ + :strategy => DoSDetector::CountingStrategy.new({ :period => 1000, :threshold => 2, :ban_period => 1 << 32 }), + :callback => DoSDetector.fallthrough_callback, + }) + mruby.handler: + Proc.new do |env| + [200, {}, ["DOS_COUNT is ", env["DOS_COUNT"], ", DOS_IP is ", env["DOS_IP"]]] + end +EOT + subtest "success" => sub { + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }s, "status"; + } + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }s, "status"; + is $body, "DOS_COUNT is 2, DOS_IP is 127.0.0.1", "content"; + } + }; +}; + +subtest "customized callback" => sub { + my $server = spawn_h2o(<< 'EOT'); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + require "share/h2o/mruby/dos_detector.rb" + DoSDetector.new({ + :strategy => DoSDetector::CountingStrategy.new({ :period => 1000, :threshold => 2, :ban_period => 1 << 32 }), + :callback => Proc.new do |env, detected, ip| + if detected + [503, {}, ["Service Unavailable"]] + else + [399, {}, []] + end + end + }) + mruby.handler: + Proc.new do |env| + [200, {}, []] + end +EOT + subtest "success" => sub { + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }s, "status"; + } + { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 503 }s, "status"; + is $body, "Service Unavailable", "content"; + } + }; +}; + +done_testing(); diff --git a/web/server/h2o/libh2o/t/50mruby-htpasswd.t b/web/server/h2o/libh2o/t/50mruby-htpasswd.t new file mode 100644 index 00000000..31cbf596 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby-htpasswd.t @@ -0,0 +1,48 @@ +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); +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'); + +subtest "basic" => sub { + my $server = spawn_h2o(<< 'EOT'); +hosts: + default: + paths: + /: + mruby.handler: | + require "share/h2o/mruby/htpasswd.rb" + Htpasswd.new("t/assets/.htpasswd", "protected space") + mruby.handler: + Proc.new do |env| + [200, {}, ["hello ", env["REMOTE_USER"], "\n"]] + end +EOT + subtest "no-auth" => sub { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 401 }s, "status"; + like $headers, qr{\r\nwww-authenticate: basic realm="protected space"\r}is, "www-authenticate header"; + unlike $body, qr/hello/; + }; + + subtest "fail" => sub { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://aaa:aaa\@127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 401 }s, "status"; + like $headers, qr{\r\nwww-authenticate: basic realm="protected space"\r}is, "www-authenticate header"; + unlike $body, qr/hello/; + }; + + subtest "success" => sub { + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://dankogai:kogaidan\@127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }s, "status"; + is $body, "hello dankogai\n", "content"; + }; +}; + +done_testing(); diff --git a/web/server/h2o/libh2o/t/50mruby-http-request.t b/web/server/h2o/libh2o/t/50mruby-http-request.t new file mode 100644 index 00000000..991d34ba --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby-http-request.t @@ -0,0 +1,269 @@ +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 /(<esi:include +src=".*?" *\\/>)/ + \@parts.each_with_index do |part, index| + if /^<esi:include +src=" *(.*?) *"/.match(part) + \@parts[index] = http_request("http://$upstream_hostport/#{\$1}") + end + end + end + def each(&block) + \@parts.each do |part| + if part.kind_of? String + block.call(part) + else + part.join[2].each(&block) + end + end + end + end + Proc.new do |env| + resp = http_request("http://$upstream_hostport/esi.html").join + resp[2] = ESIResponse.new(resp[2].join) + resp + end + /fast-path-partial: + mruby.handler: | + Proc.new do |env| + resp = http_request("http://$upstream_hostport/streaming-body").join + resp[2].each do |x| + break + end + resp + end + /async-delegate: + mruby.handler: | + Proc.new do |env| + resp = http_request("http://$upstream_hostport#{env["PATH_INFO"]}").join + if resp[0] != 200 + resp = [399, {}, []] + end + resp + end + mruby.handler: | + Proc.new do |env| + [200, {}, ["delegated!"]] + end +EOT +}); + +run_with_curl($server, sub { + my ($proto, $port, $curl_cmd) = @_; + $curl_cmd .= ' --silent --dump-header /dev/stderr'; + subtest "connection-error" => 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(); diff --git a/web/server/h2o/libh2o/t/50mruby.t b/web/server/h2o/libh2o/t/50mruby.t new file mode 100644 index 00000000..d93f17e0 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby.t @@ -0,0 +1,541 @@ +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); +use File::Temp qw(tempdir); +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'); + +subtest "handler-file" => sub { + my $server = spawn_h2o(<< 'EOT'); +hosts: + default: + paths: + /: + mruby.handler-file: t/50mruby/hello.rb +EOT + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + is $body, "hello from h2o_mruby\n"; + like $headers, qr{^HTTP/1\.1 200 OK\r\n}s; + like $headers, qr{^content-type: text/plain; charset=utf-8\r$}im; +}; + +subtest "basic" => sub { + my $server = spawn_h2o(<< 'EOT'); +hosts: + default: + paths: + /inline: + mruby.handler: | + Proc.new do |env| + [200, {}, ["hello from h2o_mruby\n"]] + end + /return-404: + mruby.handler: | + Proc.new do |env| + [404, {}, ["not found"]] + end + file.dir: examples/doc_root + /fallthru: + mruby.handler: | + Proc.new do |env| + [399, {}, []] + end + file.dir: t/50mruby/ + /echo: + mruby.handler: | + Proc.new do |env| + [200, {}, [JSON.generate(env)]] + end + /headers: + mruby.handler: | + Proc.new do |env| + [200, {"foo" => "123\n456", "bar" => "baz"}, []] + end + /headers-each: + mruby.handler: | + Proc.new do |env| + [200, [["content-type", "text/plain"], ["hello", "world"]], []] + end +EOT + my $fetch = sub { + my $path = shift; + run_prog("curl --silent -A h2o_mruby_test --dump-header /dev/stderr http://127.0.0.1:$server->{port}$path"); + }; + my ($headers, $body) = $fetch->("/inline/"); + is $body, "hello from h2o_mruby\n", "inline"; + subtest "return-404" => sub { + ($headers, $body) = $fetch->("/return-404/"); + like $headers, qr{^HTTP/1\.1 404 }is; + is $body, "not found"; + }; + subtest "fallthru" => sub { + ($headers, $body) = $fetch->("/fallthru/"); + like $headers, qr{^HTTP/1\.1 200 OK\r\n}is; + is md5_hex($body), md5_file("t/50mruby/index.html"); + }; + subtest "echo" => sub { + ($headers, $body) = $fetch->("/echo/abc?def"); + like $body, qr{"REQUEST_METHOD":"GET"}, "REQUEST_METHOD"; + like $body, qr{"SCRIPT_NAME":"/echo"}, "SCRIPT_NAME"; + like $body, qr{"PATH_INFO":"/abc"}, "PATH_INFO"; + like $body, qr{"QUERY_STRING":"def"}, "QUERY_STRING"; + like $body, qr{"SERVER_NAME":"default"}, "SERVER_NAME"; + like $body, qr{"SERVER_ADDR":"127.0.0.1"}, "SERVER_ADDR"; + like $body, qr{"SERVER_PORT":"$server->{port}"}, "SERVER_PORT"; + like $body, qr{"HTTP_HOST":"127.0.0.1:$server->{port}"}, "HTTP_HOST"; + like $body, qr{"SERVER_ADDR":"127.0.0.1"}, "REMOTE_ADDR"; + like $body, qr{"SERVER_PORT":"[0-9]+"}, "REMOTE_PORT"; + like $body, qr{"HTTP_USER_AGENT":"h2o_mruby_test"}, "HTTP_USER_AGENT"; + like $body, qr{"rack.url_scheme":"http"}, "url_scheme"; + like $body, qr{"SERVER_SOFTWARE":"h2o/[0-9]+\.[0-9]+\.[0-9]+}, "SERVER_SOFTWARE"; + }; + subtest "protocol" => sub { + run_with_curl($server, sub { + my ($proto, $port, $curl) = @_; + my $content = `$curl --silent --show-error $proto://127.0.0.1:$port/echo`; + if ($curl =~ /http2/) { + like $content, qr{"SERVER_PROTOCOL":"HTTP/2"}, "SERVER_PROTOCOL"; + } else { + like $content, qr{"SERVER_PROTOCOL":"HTTP/1\.1"}, "SERVER_PROTOCOL"; + } + }); + }; + subtest "headers" => sub { + ($headers, $body) = $fetch->("/headers/"); + like $headers, qr{^foo: 123\r$}mi; + like $headers, qr{^foo: 456\r$}mi; + like $headers, qr{^bar: baz\r$}mi; + }; + subtest "headers-each" => sub { + ($headers, $body) = $fetch->("/headers-each/"); + like $headers, qr{^content-type: text/plain\r$}mi; + like $headers, qr{^hello: world\r$}mi; + }; +}; + +subtest "reprocess_request" => sub { + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + reproxy: ON + paths: + /: + mruby.handler: | + Proc.new do |env| + [200, {"x-reproxy-url" => "http://default/dest#{env["PATH_INFO"]}"}, ["should never see this"]] + end + /307: + mruby.handler: | + Proc.new do |env| + [307, {"x-reproxy-url" => "http://default/dest#{env["PATH_INFO"]}"}, ["should never see this"]] + end + /dest: + mruby.handler: | + Proc.new do |env| + [200, {}, ["#{env["SCRIPT_NAME"]}#{env["PATH_INFO"]};#{env["CONTENT_LENGTH"]}"]] + end +EOT + my ($stderr, $stdout) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + is $stdout, "/dest/;"; + ($stderr, $stdout) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/hoge"); + is $stdout, "/dest/hoge;"; + subtest "preserve-method" => sub { + ($stderr, $stdout) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/307/"); + is $stdout, "/dest/;"; + ($stderr, $stdout) = run_prog("curl --data hello --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/307/"); + is $stdout, "/dest/;5"; + ($stderr, $stdout) = run_prog("curl --data hello --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + is $stdout, "/dest/;"; + }; +}; + +subtest "server-push" => sub { + plan skip_all => 'nghttp not found' + unless prog_exists('nghttp'); + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + push_paths = [] + if env["PATH_INFO"] == "/index.txt" + push_paths << "/index.js" + end + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join()}, []] + end + file.dir: t/assets/doc_root +EOT + my $resp = `nghttp -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + like $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\n.*\s/index\.txt}is, "receives index.js then /index.txt"; +}; + +subtest "server-push / nopush" => sub { + plan skip_all => 'nghttp not found' + unless prog_exists('nghttp'); + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + push_paths = [] + if env["PATH_INFO"] == "/index.txt" + push_paths << "/index.js" + end + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload; nopush"}.join()}, []] + end + file.dir: t/assets/doc_root +EOT + my $resp = `nghttp -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + unlike $resp, qr{/index\.js}is, "receives only /index.txt"; + like $resp, qr{/index\.txt}is, "receives only /index.txt"; +}; + +subtest "infinite-reprocess" => sub { + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + "127.0.0.1:$port": + paths: + /: + reproxy: ON + mruby.handler: | + Proc.new do |env| + [200,{"x-reproxy-url" => "http://127.0.0.1:$port/"},[]] + end +EOT + }); + my ($stderr, $stdout) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $stderr, qr{^HTTP\/1.1 502 }s, "502 response"; + like $stdout, qr{too many internal delegations}, "reason"; +}; + +subtest "send-file" => sub { + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + [200,{}, File::open("t/50mruby/index.html")] + end +EOT + my ($headers, $body) = run_prog("curl --silent -A h2o_mruby_test --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 OK\r\n}is; + is md5_hex($body), md5_file("t/50mruby/index.html"); +}; + +subtest "exception" => sub { + my $server = spawn_h2o(<< 'EOT'); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + cnt = 0 + Proc.new do |env| + cnt += 1 + if cnt % 2 != 0 + [200, {}, ["hello\n"]] + else + raise "error from rack" + end + end +EOT + my $fetch = sub { + run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}"); + }; + for (1..3) { + my ($headers, $body) = $fetch->(); + like $headers, qr{^HTTP/1\.1 200 }is; + is $body, "hello\n"; + ($headers, $body) = $fetch->(); + like $headers, qr{^HTTP/1\.1 500 }is; + } +}; + +subtest "post" => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + body = [] + 3.times do + env["rack.input"].rewind + body << env["rack.input"].read + body << "\\n" + end + [200, {}, body] + end +EOT + my ($headers, $body) = run_prog("curl --silent --data 'hello' --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 OK\r\n}is; + is $body, "hello\n" x 3; +}; + +subtest "InputStream#read-after-close" => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + prev_input = nil + Proc.new do |env| + if !prev_input + prev_input = env["rack.input"] + resp = "not cached" + else + begin + prev_input.read + resp = "must not seed this" + rescue IOError => e + resp = "got IOError" + end + end + [200, {}, [resp]] + end +EOT + my ($headers, $body) = run_prog("curl --silent --data 'hello' --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 OK\r\n}is; + is $body, "not cached"; + ($headers, $body) = run_prog("curl --silent --data 'hello' --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 OK\r\n}is; + is $body, "got IOError"; +}; + +subtest "header-concat" => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + [200, {}, [env["HTTP_COOKIE"]]] + end +EOT + run_with_curl($server, sub { + my ($proto, $port, $curl) = @_; + my ($headers, $body) = run_prog("$curl --silent -H 'cookie: a=b' -H 'cookie: c=d' --dump-header /dev/stderr $proto://127.0.0.1:$port/"); + like $headers, qr{^HTTP/\S+ 200}is; + like $body, qr{^a=b;\s*c=d$}is; + }); +}; + +subtest "close-called" => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + is_open = false + lambda do |env| + if is_open + return [500, {}, ["close not called"]] + end + is_open = true + return [ + 200, + {}, + Class.new do + def each + yield "hello" + end + define_method(:close) do + is_open = false + end + end.new, + ] + end +EOT + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }is; + is $body, "hello"; + ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }is; + is $body, "hello"; +}; + +subtest "close-called-on-exception" => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + is_open = false + lambda do |env| + if is_open + return [500, {}, ["close not called"]] + end + is_open = true + return [ + 200, + {}, + Class.new do + def each + yield "hello" + raise "yeah!" + end + define_method(:close) do + is_open = false + end + end.new, + ] + end +EOT + my ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }is; + is $body, "hello"; + ($headers, $body) = run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 200 }is; + is $body, "hello"; +}; + +subtest "log lineno" => sub { + my $tester = sub { + my ($name, $conf, $expected) = @_; + + subtest $name => sub { + my $tempdir = tempdir(CLEANUP => 1); + unlink "$tempdir/error_log"; + my $server = spawn_h2o(<< "EOT"); +$conf +error-log: $tempdir/error_log +EOT + run_prog("curl --silent --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + my @log = do { + open my $fh, "<", "$tempdir/error_log" + or die "failed to open error_log:$!"; + map { my $l = $_; chomp $l; $l } <$fh>; + }; + @log = grep { $_ =~ /^\[h2o_mruby\]/ } @log; + like $log[$#log], qr{\[h2o_mruby\] in request:/:mruby raised: @{[$server->{conf_file}]}:$expected:\s*hoge \(RuntimeError\)}; + }; + }; + $tester->("flow style", <<"EOT", 5); +hosts: + default: + paths: + /: + mruby.handler: Proc.new do |env| raise "hoge" end +EOT + $tester->("block style", <<"EOT", 7); +hosts: + default: + paths: + /: + mruby.handler: | + Proc.new do |env| + raise "hoge" + end +EOT +}; + +subtest 'response with specific statuses should not contain content-length header' => sub { + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + proc {|env| + [204, {}, []] + } +EOT + my ($headers, $body) = run_prog("curl --silent --data 'hello' --dump-header /dev/stderr http://127.0.0.1:$server->{port}/"); + like $headers, qr{^HTTP/1\.1 204 OK\r\n}is; + unlike $headers, qr{^content-length:}im; +}; + +subtest 'PATH_INFO and SCRIPT_NAME' => sub { + plan skip_all => "nc not found" + unless prog_exists("nc"); + + my $server = spawn_h2o(<< "EOT"); +num-threads: 1 +hosts: + default: + paths: + /: + mruby.handler: | + proc {|env| + [200, {}, ['handler1, ' + env['SCRIPT_NAME'] + ', ' + env['PATH_INFO']]] + } + /abc: + mruby.handler: | + proc {|env| + [200, {}, ['handler2, ' + env['SCRIPT_NAME'] + ', ' + env['PATH_INFO']]] + } + "/foo bar": + mruby.handler: | + proc {|env| + [200, {}, ['handler3, ' + env['SCRIPT_NAME'] + ', ' + env['PATH_INFO']]] + } +EOT + my $nc = sub { + my $path = shift; + my $cmd = "echo 'GET $path HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\n\\r' | nc 127.0.0.1 $server->{port}"; + (undef, my $r) = run_prog($cmd); + split(/\r\n\r\n/, $r, 2); + }; + + my $body; + (undef, $body) = $nc->('/abc/def%20ghi'); + is $body, 'handler2, /abc, /def%20ghi', 'should be kept undecoded'; + + (undef, $body) = $nc->('/abc/def/../ghi/../jhk'); + is $body, 'handler2, /abc, /def/../ghi/../jhk', 'https://github.com/h2o/h2o/pull/1480#issuecomment-339614160'; + + (undef, $body) = $nc->('/123/../abc/def/../ghi'); + is $body, 'handler2, /abc, /def/../ghi', 'https://github.com/h2o/h2o/pull/1480#issuecomment-339658134'; + + (undef, $body) = $nc->('/foo%20bar/baz'); + is $body, 'handler3, /foo bar, /baz', 'paths should be decoded'; + + (undef, $body) = $nc->('/xxx/../hoge'); + is $body, 'handler1, , /xxx/../hoge', 'string size is too big issue 1'; + + (undef, $body) = $nc->('/../abc'); + is $body, 'handler2, /abc, ', 'string size is too big issue 2'; + + (undef, $body) = $nc->('abc'); + is $body, 'handler2, /abc, ', 'no leading slash 1'; + + (undef, $body) = $nc->('abc/def'); + is $body, 'handler2, /abc, /def', 'no leading slash 2'; + + (undef, $body) = $nc->('123/../abc/def/../ghi'); + is $body, 'handler2, /abc, /def/../ghi', 'no leading slash 3'; + + (undef, $body) = $nc->('xyz'); + is $body, 'handler1, , xyz', 'no leading slash 4'; + + (undef, $body) = $nc->(''); + is $body, 'handler1, , ', 'empty path'; +}; + +done_testing(); diff --git a/web/server/h2o/libh2o/t/50mruby/hello.rb b/web/server/h2o/libh2o/t/50mruby/hello.rb new file mode 100644 index 00000000..34f32140 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby/hello.rb @@ -0,0 +1,3 @@ +Proc.new do |env| + [200, {"content-type" => "text/plain; charset=utf-8"}, ["hello from h2o_mruby\n"]] +end diff --git a/web/server/h2o/libh2o/t/50mruby/index.html b/web/server/h2o/libh2o/t/50mruby/index.html new file mode 100644 index 00000000..d0287e65 --- /dev/null +++ b/web/server/h2o/libh2o/t/50mruby/index.html @@ -0,0 +1 @@ +I'm index.html |