This prevents public-inbox-httpd from buffering ->getline
results from a static file into another temporary file when
writing to slow clients. Instead we inject the static file
ref with offsets and length directly into the {wbuf} queue.
It took me a while to decide to go this route, some
rejected ideas:
1. Using Plack::Util::set_io_path and having PublicInbox::HTTP
serve the result directly. This is compatible with what
some other PSGI servers do using sendfile. However, neither
Starman or Twiggy currently use sendfile for partial responses.
2. Parsing the Content-Range response header for offsets and
lengths to use with set_io_path for partial responses.
These rejected ideas required increasing the complexity of HTTP
response writing in PublicInbox::HTTP in the common, non-static
file cases. Instead, we made minor changes to the colder write
buffering path of PublicInbox::DS and leave the hot paths
untouched.
We still support generic PSGI servers via ->getline. However,
since we don't know the characteristics of other PSGI servers,
we no longer do a 64K initial read in an attempt to negotiate a
larger TCP window.
+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;
my ($env, $in, $h, $beg, $end, $size) = @_;
my $code = 200;
my $len = $size;
if ($len <= 0) {
$code = 416;
} else {
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";
push @$h, qw(Accept-Ranges bytes Content-Range);
push @$h, "bytes $beg-$end/$size";
push @$h, 'Content-Range', "bytes */$size";
return [ 416, $h, [] ];
}
push @$h, 'Content-Range', "bytes */$size";
return [ 416, $h, [] ];
}
}
# returns a PSGI arrayref response iff .gz and non-.gz mtimes match
}
# returns a PSGI arrayref response iff .gz and non-.gz mtimes match
my $len = $size;
my $code = 200;
push @$h, 'Content-Type', $type;
my $len = $size;
my $code = 200;
push @$h, 'Content-Type', $type;
if (($env->{HTTP_RANGE} || '') =~ /\bbytes=([0-9]*)-([0-9]*)\z/) {
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;
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) = @_;
}
# 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 $len = $self->{len} or return; # undef, tells server we're done
- my $n = delete($self->{initial_rd}) // 8192;
- my $r = sysread($self->{in}, my $buf, $n);
+ seek($self->{in}, $self->{off}, SEEK_SET) or
+ die "seek ($self->{path}): $!";
+ my $r = read($self->{in}, my $buf, $n);
if (defined $r && $r > 0) { # success!
$self->{len} = $len - $r;
if (defined $r && $r > 0) { # success!
$self->{len} = $len - $r;
return $buf;
}
my $m = defined $r ? "EOF with $len bytes left" : "read error: $!";
return $buf;
}
my $m = defined $r ? "EOF with $len bytes left" : "read error: $!";