Hyperman

 view release on metacpan or  search on metacpan

t/73-h2-edges.t  view on Meta::CPAN

#!perl
use strict;
use warnings;
use lib 't/lib';
use Test::More;
use HMTest qw(free_ports quiet_child server_reap);
use IO::Socket::INET;
use Time::HiRes ();
use File::Temp ();
use Hyperman;

# HTTP/2 edges: a file body lifted onto the stream and an object body
# slurped, hop-by-hop headers dropped, a header the library refuses, the
# responder's three-element form and a second call, handlers that die before
# and after producing, a response of the wrong shape, foreign and failed
# futures, a stream reset while its future is pending, a response past the
# write mark, a stream opened after parking, the upgrade's settings with
# whitespace and with a bad length, and raw frames: a preface in two writes,
# a Host beside :authority, trailers, a header block past the ceiling, and a
# GOAWAY from the client.

plan skip_all => 'HTTP/2 support not built' unless Hyperman->has_http2;
plan skip_all => 'fork is not Windows' if $^O eq 'MSWin32';
my $curl;
for my $c ('/opt/homebrew/opt/curl/bin/curl', '/usr/local/opt/curl/bin/curl', split /\n/, `which -a curl 2>/dev/null` || '') {
    next unless $c && -x $c;
    next unless (`$c --version 2>/dev/null` || '') =~ /^Features:.*\bHTTP2\b/m;
    $curl = $c;
    last;
}
plan skip_all => 'no HTTP/2-capable curl' unless $curl;

my ($pa, $pb) = free_ports(2);
plan skip_all => 'no free loopback ports' unless $pa && $pb;
my $dir = File::Temp::tempdir(CLEANUP => 1);
sub slurp { my $f = shift; -f $f ? do { local (@ARGV, $/) = ($f); <> } // '' : '' }
sub wait_for { my ($file, $re) = @_; for (1 .. 50) { my $t = slurp($file); return $t if $t =~ $re; Time::HiRes::sleep(0.1) } return slurp($file) }

package Duck;
sub new { my ($class, %o) = @_; bless { %o }, $class }
sub on_ready { my ($self, $cb) = @_; $cb->($self); $self }
sub is_ready { 1 }
sub is_done { 1 }
sub is_failed { 0 }
sub is_cancelled { 0 }
sub get { my $self = shift; die "duck get dies\n" if $self->{dies}; $self->{value} }
sub failure { () }
sub cancel { $_[0] }
package Getliner;
sub new { my ($class, $s) = @_; bless { s => $s, done => 0 }, $class }
sub getline { my $self = shift; return undef if $self->{done}++; $self->{s} }
sub read { my $self = shift; return 0 if $self->{done}++; $_[0] = $self->{s}; length $self->{s} }
sub close { 1 }
package main;

my $note = "$dir/note";
my $keep;
open my $ffh, '>', "$dir/file.bin" or die; print $ffh 'f' x 65536; close $ffh;
my $app = sub {
    my $env = shift;
    my $p = $env->{PATH_INFO};
    my $ok = sub { [ 200, [ 'Content-Type' => 'text/plain' ], [ @_ ] ] };
    if ($p eq '/detach') { my $err = eval { Hyperman::detach($env); 'none' } // $@; return $ok->($err) }
    if ($p eq '/file') { open my $fh, '<', "$dir/file.bin" or die; return [ 200, [ 'Content-Length' => 65536 ], $fh ] }
    if ($p eq '/obj') { return [ 200, [ 'Content-Type' => 'text/plain' ], Getliner->new('from-getline') ] }
    if ($p eq '/hop') { return [ 200, [ 'Keep-Alive' => 'timeout=5', 'Proxy-Connection' => 'close', 'Transfer-Encoding' => 'chunked', 'Upgrade' => 'x', 'TE' => 'trailers', 'Connection' => 'close', 'X-Kept' => 'yes' ], ['hop'] ] }
    if ($p eq '/badhdr') { return [ 200, [ 'bad name' => 'x' ], ['bad'] ] }
    if ($p eq '/resp3') { return sub { $_[0]->([ 200, [ 'Content-Type' => 'text/plain' ], ['three'] ]) } }
    if ($p eq '/resp_twice') {
        return sub {
            my $r = shift;
            my $w = $r->([ 200, [ 'Content-Type' => 'text/plain' ] ]);
            my $err = eval { $r->([ 200, [], ['again'] ]); 'none' } // $@;
            open my $fh, '>', $note or die; print $fh $err; close $fh;
            $w->write('once'); $w->close;
        };
    }
    if ($p eq '/die_producing') { return sub { my $w = Hyperman::stream($env, 200, [ 'Content-Type' => 'text/plain' ]); $w->write('x'); $keep = $w; die "producing boom\n" } }
    if ($p eq '/die_early') { return sub { die "early boom\n" } }
    if ($p eq '/junk') { return { not => 'a response' } }
    if ($p eq '/duck') { return Duck->new(value => $ok->('duck')) }
    if ($p eq '/duck_dies') { return Duck->new(dies => 1) }
    if ($p eq '/fail') { return Hyperman::Future->fail_future("async nope\n") }
    if ($p eq '/badval') { return Hyperman::Future->done_future('not a response') }
    if ($p eq '/slow') { return Hyperman->timer(3)->then(sub { $ok->('late') }) }
    if ($p eq '/big') { return $ok->('b' x (4 * 1024 * 1024)) }
    if ($p eq '/stream_sync') {
        Hyperman->timer(0.01)->get;
        my $w = Hyperman::stream($env, 200, [ 'Content-Type' => 'text/plain' ]);
        $w->write('synced'); $w->close;
        return [ 101, [], [] ];
    }
    if ($p eq '/echo') {
        my $body = '';
        $env->{'psgi.input'}->read($body, $env->{CONTENT_LENGTH} || 1 << 20);
        return $ok->('len=' . length($body));



( run in 2.344 seconds using v1.01-cache-2.11-cpan-145b269f2f3 )