From 58daab21cd043e1dc37024a7f99b396788372918 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 9 Mar 2024 14:19:48 +0100 Subject: Merging upstream version 1.44.3. Signed-off-by: Daniel Baumann --- web/server/h2o/libh2o/t/40server-push.t | 245 ++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 web/server/h2o/libh2o/t/40server-push.t (limited to 'web/server/h2o/libh2o/t/40server-push.t') diff --git a/web/server/h2o/libh2o/t/40server-push.t b/web/server/h2o/libh2o/t/40server-push.t new file mode 100644 index 000000000..519d1be80 --- /dev/null +++ b/web/server/h2o/libh2o/t/40server-push.t @@ -0,0 +1,245 @@ +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'); +plan skip_all => 'Starlet not found' + unless system('perl -MStarlet /dev/null > /dev/null 2>&1') == 0; +plan skip_all => 'nghttp not found' + unless prog_exists('nghttp'); +plan skip_all => 'mruby support is off' + unless server_features()->{mruby}; + +subtest "basic" => sub { + # spawn upstream + my $upstream_port = empty_port(); + my $upstream = spawn_server( + argv => [ + qw(plackup -s Starlet --access-log /dev/null -p), $upstream_port, ASSETS_DIR . "/upstream.psgi", + ], + is_ready => sub { + check_port($upstream_port); + }, + ); + # spawn server + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + proxy.reverse.url: http://127.0.0.1:$upstream_port + /mruby: + mruby.handler: | + Proc.new do |env| + [399, { "link" => "; rel=preload" }, [] ] + end + proxy.reverse.url: http://127.0.0.1:$upstream_port + /assets: + file.dir: @{[DOC_ROOT]} +EOT + + my $doit = sub { + my ($proto, $opts, $port) = @_; + subtest 'push-prioritized' => sub { + my $resp = `nghttp $opts -n --stat '$proto://127.0.0.1:$port/index.txt?resp:link=\%3b\%20rel=preload'`; + like $resp, qr{\nid\s*responseEnd\s.*\s/assets/index\.js\n.*\s/index\.txt\?}is; + }; + subtest 'push-unprioritized' => sub { + my $resp = `nghttp $opts -n --stat '$proto://127.0.0.1:$port/index.txt?resp:link=\%3b\%20rel=preload'`; + like $resp, qr{\nid\s*responseEnd\s.*\s/index\.txt\?.*\s/index\.txt.gz\n}is; + }; + subtest "push-1xx" => sub { + my $out = `nghttp $opts -n --stat '$proto://127.0.0.1:$port/1xx-push/'`; + # index.js arrives < 100ms, and /1xx-push/ arrives > 1sec + $out = (split /^.*?\nid *responseEnd .*?\n/s, $out, 2)[1]; + chomp $out; + my @responses = split /\n/, $out; + is scalar(@responses), 2, "2 responses"; + like $responses[0], qr{\+[0-9]{1,2}\.[0-9]*ms .* /index.js$}, "index.js arrives < 100ms"; + like $responses[1], qr{\+1\.[0-9]*s .* /1xx-push/$}, "/1xx-push/ arrives >= 1sec"; + }; + subtest 'push-while-sleep' => sub { + my $resp = `nghttp $opts -n --stat '$proto://127.0.0.1:$port/mruby/sleep-and-respond?sleep=1'`; + like $resp, qr{\nid\s*responseEnd\s.*\s/index\.txt\.gz\n.*\s/mruby/sleep-and-respond}is; + }; + }; + + subtest 'h2 direct' => sub { + $doit->('http', '', $server->{port}); + }; + subtest 'h2 upgrade' => sub { + $doit->('http', '-u', $server->{port}); + }; + subtest 'h2c' => sub { + $doit->('https', '', $server->{tls_port}); + }; +}; + +subtest "push-twice" => sub { + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + "127.0.0.1:$tls_port": + paths: + /: + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/index.txt" + push_paths = [] + push_paths << "/index.js" + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join("\\n")}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root +EOT + }); + my $resp = `nghttp -v -m 2 -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + like $resp, qr{\s+200\s+16\s+/index\.js\n}is, "receives index.js"; + unlike $resp, qr{\s+200\s+16\s+/index\.js\n.*\s+200\s+16\s+/index\.js\n}is, "receives index.js only once"; +}; + + +subtest "push-after-reproxy" => sub { + subtest "authority-match" => sub { + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + "127.0.0.1:$tls_port": + paths: + /: + reproxy: ON + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/reproxy" + [307, {"x-reproxy-url" => "/index.txt"}, ["should never see this"]] + when "/index.txt" + push_paths = [] + push_paths << "/index.js" + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join("\\n")}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root +EOT + }); + my $resp = `nghttp -n --stat https://127.0.0.1:$server->{tls_port}/reproxy`; + like $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\n.*\s/reproxy}is, "receives index.js then /reproxy"; + }; + subtest "authority-mismatch" => sub { + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + default: + paths: + /: + reproxy: ON + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/reproxy" + [307, {"x-reproxy-url" => "/index.txt"}, ["should never see this"]] + when "/index.txt" + push_paths = [] + push_paths << "/index.js?1" + push_paths << "https://127.0.0.1:$tls_port/index.js?2" + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join("\\n")}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root +EOT + }); + my $resp = `nghttp -n --stat https://127.0.0.1:$server->{tls_port}/reproxy`; + like $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\?2\n.*\s/reproxy}is, "receives index.js?2 then /reproxy"; + unlike $resp, qr{/index\.js\?1\n}is, "index.js?1 not received (authority mismatch)"; + }; +}; + +subtest "casper" => sub { + subtest "custom capacity-bits" => sub { + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + "127.0.0.1:$tls_port": + http2-casper: + capacity-bits: 11 + paths: + /: + reproxy: ON + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/index.txt" + push_paths = [] + push_paths << "/index.js" + [399, push_paths.empty? ? {} : {"link" => push_paths.map{|p| "<#{p}>; rel=preload"}.join("\\n")}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root +EOT + }); + my $resp = `nghttp -v -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"; + my ($casper) = ($resp =~ qr{set-cookie:\s*(h2o_casper=[^\s;]+)}i); + + is length($casper), 17; + + { + my $resp = `nghttp -H'cookie: $casper' -v -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + unlike $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\n}is, "does not receives index.js"; + } + }; +}; + +subtest "cache-digest" => sub { + plan skip_all => 'node not found' + unless prog_exists('node'); + my $server = spawn_h2o(sub { + my ($port, $tls_port) = @_; + return << "EOT"; +hosts: + "127.0.0.1:$tls_port": + paths: + /: + mruby.handler: | + Proc.new do |env| + case env["PATH_INFO"] + when "/index.txt" + [399, {"link" => "; rel=preload"}, []] + else + [399, {}, []] + end + end + file.dir: t/assets/doc_root +EOT + }); + my $calc_digest = sub { + my $cmd = "node misc/cache-digest.js/cli.js -b @{[join ' ', map { qq{https://127.0.0.1:$server->{tls_port}$_} } @_]}"; + my $digest = `$cmd`; + chomp $digest; + return "$digest; complete"; + }; + my $resp = `nghttp -H'cache-digest: @{[$calc_digest->()]}' -v -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"; + $resp = `nghttp -H'cache-digest: @{[$calc_digest->("/index.js", "/style.css")]}' -v -n --stat https://127.0.0.1:$server->{tls_port}/index.txt`; + unlike $resp, qr{\nid\s*responseEnd\s.*\s/index\.js\n}is, "does not receive index.js"; + $resp = `nghttp -H'cache-digest: @{[$calc_digest->("/script.js", "/style.css")]}' -v -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"; +}; + +done_testing; -- cgit v1.2.3