use HTTP::Status qw(status_message);
use HTTP::Date qw(time2str);
use IO::File;
-my $null_io = IO::File->new('/dev/null', '<');
use constant {
CHUNK_START => -1, # [a-f0-9]+\r\n
CHUNK_END => -2, # \r\n
CHUNK_MAX_HDR => 256,
};
+my $null_io = IO::File->new('/dev/null', '<');
+my $http_date;
+my $prev = 0;
+sub http_date () {
+ my $now = time;
+ $now == $prev ? $http_date : ($http_date = time2str($prev = $now));
+}
+
sub new ($$$) {
my ($class, $sock, $addr, $httpd) = @_;
my $self = fields::new($class);
return event_read_input($self) if defined $self->{env};
- my $off = $self->{rbuf} eq '' ? 0 : length($self->{rbuf});
+ my $off = length($self->{rbuf});
my $r = sysread($self->{sock}, $self->{rbuf}, 8192, $off);
if (defined $r) {
return $self->close if $r == 0;
# We do not support Trailers in chunked requests, for now
# (they are rarely-used and git (as of 2.7.2) does not use them)
- return $self->quit(400) if $r == -1 || $env{HTTP_TRAILER};
+ return quit($self, 400) if $r == -1 || $env{HTTP_TRAILER};
return $self->watch_read(1) if $r < 0; # incomplete
$self->{rbuf} = substr($self->{rbuf}, $r);
my $len = input_prepare($self, \%env);
while ($len > 0) {
if ($$rbuf ne '') {
my $w = write_in_full($input, $rbuf, $len);
- return $self->write_err unless $w;
+ return write_err($self) unless $w;
$len -= $w;
die "BUG: $len < 0 (w=$w)" if $len < 0;
if ($len == 0) { # next request may be pipelined
$$rbuf = '';
}
my $r = sysread($sock, $$rbuf, 8192);
- return $self->recv_err($r, $len) unless $r;
+ return recv_err($self, $r, $len) unless $r;
# continue looping if $r > 0;
}
app_dispatch($self);
my ($self) = @_;
$self->watch_read(0);
my $env = $self->{env};
- $self->{env} = undef;
$env->{REMOTE_ADDR} = $self->peer_ip_string; # Danga::Socket
$env->{REMOTE_PORT} = $self->{peer_port}; # set by peer_ip_string
if (my $host = $env->{HTTP_HOST}) {
$host =~ s/:(\d+)\z// and $env->{SERVER_PORT} = $1;
$env->{SERVER_NAME} = $host;
}
- $env->{'psgi.input'}->seek(0, SEEK_SET);
+ sysseek($env->{'psgi.input'}, 0, SEEK_SET) or die "input seek failed: $!";
my $res = Plack::Util::run_app($self->{httpd}->{app}, $env);
eval {
if (ref($res) eq 'CODE') {
($conn =~ /\bkeep-alive\b/i);
$h .= 'Connection: ' . ($alive ? 'keep-alive' : 'close');
- $h .= "\r\nDate: " . time2str(time) . "\r\n\r\n";
+ $h .= "\r\nDate: " . http_date() . "\r\n\r\n";
if (($len || $chunked) && $env->{REQUEST_METHOD} ne 'HEAD') {
more($self, $h);
} else {
$self->write(sub { $self->close });
}
+ $self->{env} = undef;
};
if (defined $res->[2]) {
# only continue watching for readability when we are done writing:
return if $self->write(undef) != 1;
- if ($self->{rbuf} eq '') {
+ if ($self->{rbuf} eq '') { # wait for next request
$self->watch_read(1);
- } else {
- # avoid recursion
+ } else { # avoid recursion for pipelined requests
Danga::Socket->AddTimer(0, sub { rbuf_process($self) });
}
}
my $err = $self->{env}->{'psgi.errors'};
my $msg = $! || '(zero write)';
$err->print("error buffering to input: $msg\n");
- $self->quit(500);
+ quit($self, 500);
}
sub recv_err {
}
my $err = $self->{env}->{'psgi.errors'};
$err->print("error reading for input: $! ($len bytes remaining)\n");
- $self->quit(500);
+ quit($self, 500);
}
sub write_in_full {
while (1) { # chunk start
if ($len == CHUNK_ZEND) {
return app_dispatch($self) if $$rbuf =~ s/\A\r\n//s;
- return $self->quit(400) if length($$rbuf) > 2;
+ return quit($self, 400) if length($$rbuf) > 2;
}
if ($len == CHUNK_END) {
if ($$rbuf =~ s/\A\r\n//s) {
$len = CHUNK_START;
} elsif (length($$rbuf) > 2) {
- return $self->quit(400);
+ return quit($self, 400);
}
}
if ($len == CHUNK_START) {
if ($$rbuf =~ s/\A([a-f0-9]+).*?\r\n//i) {
$len = hex $1;
} elsif (length($$rbuf) > CHUNK_MAX_HDR) {
- return $self->quit(400);
+ return quit($self, 400);
}
# will break from loop since $len >= 0
}
if ($len < 0) { # chunk header is trickled, read more
my $off = length($$rbuf);
my $r = sysread($sock, $$rbuf, 8192, $off);
- return $self->recv_err($r, $len) unless $r;
+ return recv_err($self, $r, $len) unless $r;
# (implicit) goto chunk_start if $r > 0;
}
$len = CHUNK_ZEND if $len == 0;
until ($len <= 0) {
if ($$rbuf ne '') {
my $w = write_in_full($input, $rbuf, $len);
- return $self->write_err unless $w;
+ return write_err($self) unless $w;
$len -= $w;
if ($len == 0) {
# we may have leftover data to parse
if ($$rbuf eq '') {
# read more of current chunk
my $r = sysread($sock, $$rbuf, 8192);
- return $self->recv_err($r, $len) unless $r;
+ return recv_err($self, $r, $len) unless $r;
}
}
}
sub event_hup { $_[0]->close }
sub event_err { $_[0]->close }
+# for graceful shutdown in PublicInbox::Daemon:
+sub busy () {
+ my ($self) = @_;
+ ($self->{rbuf} ne '' || $self->{env} || $self->{write_buf_size});
+}
+
1;