X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FWwwStatic.pm;h=0c534b46f0d546cdfd28c35138a3140664acf647;hb=95bdac7f09c69036efed537a4d03d5bdd2ae4eb6;hp=bc42236e57b6e6442db9c7351e76587f68d3d871;hpb=078f637c80cc33c7d29973b95fdc16205ad7bb32;p=public-inbox.git diff --git a/lib/PublicInbox/WwwStatic.pm b/lib/PublicInbox/WwwStatic.pm index bc42236e..0c534b46 100644 --- a/lib/PublicInbox/WwwStatic.pm +++ b/lib/PublicInbox/WwwStatic.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2019 all contributors +# Copyright (C) 2016-2020 all contributors # License: AGPL-3.0+ # This package can either be a PSGI response body for a static file @@ -12,7 +12,7 @@ use strict; use parent qw(Exporter); use bytes (); use Fcntl qw(SEEK_SET O_RDONLY O_NONBLOCK); -use POSIX qw(strftime lround); +use POSIX qw(strftime); use HTTP::Date qw(time2str); use HTTP::Status qw(status_message); use Errno qw(EACCES ENOTDIR ENOENT); @@ -51,7 +51,19 @@ sub r ($;$) { [ $msg ] ] } -sub prepare_range { +sub getline_response ($$$$$) { + my ($env, $in, $off, $len, $path) = @_; + my $r = bless {}, __PACKAGE__; + if ($env->{'pi-httpd.async'}) { # public-inbox-httpd-only mode + $env->{'psgix.no-compress'} = 1; # do not chunk response + %$r = ( bypass => [$in, $off, $len, $env->{'psgix.io'}] ); + } else { + %$r = ( in => $in, off => $off, len => $len, path => $path ); + } + $r; +} + +sub setup_range { my ($env, $in, $h, $beg, $end, $size) = @_; my $code = 200; my $len = $size; @@ -81,9 +93,6 @@ sub prepare_range { if ($len <= 0) { $code = 416; } else { - if ($in) { - sysseek($in, $beg, SEEK_SET) or return r(500); - } push @$h, qw(Accept-Ranges bytes Content-Range); push @$h, "bytes $beg-$end/$size"; @@ -95,7 +104,7 @@ sub prepare_range { push @$h, 'Content-Range', "bytes */$size"; return [ 416, $h, [] ]; } - ($code, $len); + ($code, $beg, $len); } # returns a PSGI arrayref response iff .gz and non-.gz mtimes match @@ -142,43 +151,41 @@ sub response ($$$;$) { my $len = $size; my $code = 200; push @$h, 'Content-Type', $type; + my $off = 0; if (($env->{HTTP_RANGE} || '') =~ /\bbytes=([0-9]*)-([0-9]*)\z/) { - ($code, $len) = prepare_range($env, $in, $h, $1, $2, $size); + ($code, $off, $len) = setup_range($env, $in, $h, $1, $2, $size); return $code if ref($code); } push @$h, 'Content-Length', $len, 'Last-Modified', $mtime; - my $body = $in ? bless { - initial_rd => 65536, - len => $len, - in => $in, - path => $path, - env => $env, - }, __PACKAGE__ : []; - [ $code, $h, $body ]; + [ $code, $h, $in ? getline_response($env, $in, $off, $len, $path) : [] ] } # called by PSGI servers on each response chunk: sub getline { my ($self) = @_; + + # avoid buffering, by becoming the buffer! (public-inbox-httpd) + if (my $tmpio = delete $self->{bypass}) { + my $http = pop @$tmpio; # PublicInbox::HTTP + push @{$http->{wbuf}}, $tmpio; # [ $in, $off, $len ] + $http->flush_write; + return; # undef, EOF + } + + # generic PSGI runs this: my $len = $self->{len} or return; # undef, tells server we're done - my $n = delete($self->{initial_rd}) // 8192; + my $n = 8192; $n = $len if $len < $n; + sysseek($self->{in}, $self->{off}, SEEK_SET) or + die "sysseek ($self->{path}): $!"; my $r = sysread($self->{in}, my $buf, $n); if (defined $r && $r > 0) { # success! $self->{len} = $len - $r; + $self->{off} += $r; return $buf; } my $m = defined $r ? "EOF with $len bytes left" : "read error: $!"; - my $env = $self->{env}; - $env->{'psgi.errors'}->print("$self->{path} $m\n"); - - # drop the client on error - if (my $io = $env->{'psgix.io'}) { - $io->close; # this is likely PublicInbox::DS::close - } else { # for some PSGI servers w/o psgix.io - die "dropping client socket\n"; - } - undef; + die "$self->{path} $m, dropping client socket\n"; } sub close {} # noop, called by PSGI server, just let everything go out-of-scope @@ -238,7 +245,7 @@ sub human_size ($) { last; } } - lround($size).$suffix; + sprintf('%lu', $size).$suffix; } # by default, this returns "index.html" if it exists for a given directory