]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/HTTP.pm
http: get rid of anonymous subs for write/close
[public-inbox.git] / lib / PublicInbox / HTTP.pm
index 680be72b15a28c9b208ba62db33dd11e1c9b0563..ad1a2f9f5e2e850e5f9e0eec0f62ebecf0b2dca3 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2018 all contributors <meta@public-inbox.org>
+# Copyright (C) 2016-2019 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 #
 # Generic PSGI server for convenience.  It aims to provide
@@ -11,16 +11,16 @@ package PublicInbox::HTTP;
 use strict;
 use warnings;
 use base qw(PublicInbox::DS);
-use fields qw(httpd env input_left remote_addr remote_port forward pull);
+use fields qw(httpd env input_left remote_addr remote_port forward);
 use bytes (); # only for bytes::length
 use Fcntl qw(:seek);
 use Plack::HTTPParser qw(parse_http_request); # XS or pure Perl
 use HTTP::Status qw(status_message);
 use HTTP::Date qw(time2str);
 use IO::Handle;
-require PublicInbox::EvCleanup;
-PublicInbox::DS->import(qw(msg_more));
+use PublicInbox::DS qw(msg_more);
 use PublicInbox::Syscall qw(EPOLLIN EPOLLONESHOT);
+use PublicInbox::Tmpfile;
 use constant {
        CHUNK_START => -1,   # [a-f0-9]+\r\n
        CHUNK_END => -2,     # \r\n
@@ -222,22 +222,20 @@ sub response_header_write {
 }
 
 # middlewares such as Deflater may write empty strings
-sub chunked_wcb ($) {
-       my ($self) = @_;
-       sub {
-               return if $_[0] eq '';
-               msg_more($self, sprintf("%x\r\n", bytes::length($_[0])));
-               msg_more($self, $_[0]);
-
-               # use $self->write(\"\n\n") if you care about real-time
-               # streaming responses, public-inbox WWW does not.
-               msg_more($self, "\r\n");
-       }
+sub chunked_write ($$) {
+       my $self = $_[0];
+       return if $_[1] eq '';
+       msg_more($self, sprintf("%x\r\n", bytes::length($_[1])));
+       msg_more($self, $_[1]);
+
+       # use $self->write(\"\n\n") if you care about real-time
+       # streaming responses, public-inbox WWW does not.
+       msg_more($self, "\r\n");
 }
 
-sub identity_wcb ($) {
-       my ($self) = @_;
-       sub { $self->write(\($_[0])) if $_[0] ne '' }
+sub identity_write ($$) {
+       my $self = $_[0];
+       $self->write(\($_[1])) if $_[1] ne '';
 }
 
 sub next_request ($) {
@@ -251,81 +249,87 @@ sub next_request ($) {
        }
 }
 
-sub response_done_cb ($$) {
+sub response_done {
        my ($self, $alive) = @_;
-       sub {
-               my $env = delete $self->{env};
-               $self->write(\"0\r\n\r\n") if $alive == 2;
-               $self->write($alive ? \&next_request : \&close);
-       }
+       delete $self->{env}; # we're no longer busy
+       $self->write(\"0\r\n\r\n") if $alive == 2;
+       $self->write($alive ? \&next_request : \&close);
 }
 
-sub getline_cb ($$$) {
-       my ($self, $write, $close) = @_;
-       local $/ = \8192;
-       my $forward = $self->{forward};
-       # limit our own running time for fairness with other
-       # clients and to avoid buffering too much:
-       if ($forward) {
-               my $buf = eval { $forward->getline };
+sub getline_response ($$) {
+       my ($self, $alive) = @_;
+       my $write = $alive == 2 ? \&chunked_write : \&identity_write;
+       my $pull; # DANGER: self-referential
+       $pull = sub {
+               my $forward = $self->{forward};
+               # limit our own running time for fairness with other
+               # clients and to avoid buffering too much:
+               my $buf = eval {
+                       local $/ = \8192;
+                       $forward->getline;
+               } if $forward;
+
                if (defined $buf) {
-                       $write->($buf); # may close in PublicInbox::DS::write
+                       # may close in PublicInbox::DS::write
+                       $write->($self, $buf);
+
                        if ($self->{sock}) {
-                               my $next = $self->{pull};
-                               if ($self->{wbuf}) {
-                                       $self->write($next);
-                               } else {
-                                       PublicInbox::DS::requeue($next);
-                               }
-                               return;
+                               my $wbuf = $self->{wbuf} ||= [];
+                               push @$wbuf, $pull;
+
+                               # wbuf may be populated by $write->(...$buf),
+                               # no need to rearm if so:
+                               $self->requeue if scalar(@$wbuf) == 1;
+                               return; # likely
                        }
                } elsif ($@) {
                        err($self, "response ->getline error: $@");
-                       $forward = undef;
                        $self->close;
                }
-       }
 
-       delete @$self{qw(forward pull)};
-       # avoid recursion
-       if ($forward) {
-               eval { $forward->close };
-               if ($@) {
-                       err($self, "response ->close error: $@");
-                       $self->close; # idempotent
+               $pull = undef; # all done!
+               # avoid recursion
+               if (delete $self->{forward}) {
+                       eval { $forward->close };
+                       if ($@) {
+                               err($self, "response ->close error: $@");
+                               $self->close; # idempotent
+                       }
                }
-       }
-       $close->();
-}
+               $forward = undef;
+               response_done($self, $alive);
+       };
 
-sub getline_response ($$$) {
-       my ($self, $write, $close) = @_;
-       my $pull = $self->{pull} = sub { getline_cb($self, $write, $close) };
-       $pull->();
+       $pull->(); # kick-off!
 }
 
 sub response_write {
        my ($self, $env, $res) = @_;
        my $alive = response_header_write($self, $env, $res);
-       my $close = response_done_cb($self, $alive);
-       my $write = $alive == 2 ? chunked_wcb($self) : identity_wcb($self);
        if (defined(my $body = $res->[2])) {
                if (ref $body eq 'ARRAY') {
-                       $write->($_) foreach @$body;
-                       $close->();
+                       if ($alive == 2) {
+                               chunked_write($self, $_) for @$body;
+                       } else {
+                               identity_write($self, $_) for @$body;
+                       }
+                       response_done($self, $alive);
                } else {
                        $self->{forward} = $body;
-                       getline_response($self, $write, $close);
+                       getline_response($self, $alive);
                }
+       # these are returned to the calling application:
+       } elsif ($alive == 2) {
+               bless [ $self, $alive ], 'PublicInbox::HTTP::Chunked';
        } else {
-               # this is returned to the calling application:
-               Plack::Util::inline_object(write => $write, close => $close);
+               bless [ $self, $alive ], 'PublicInbox::HTTP::Identity';
        }
 }
 
 sub input_tmpfile ($) {
-       open($_[0], '+>', undef);
-       $_[0]->autoflush(1);
+       my $input = tmpfile('http.input', $_[0]->{sock}) or return;
+       $input->autoflush(1);
+       $input;
 }
 
 sub input_prepare {
@@ -337,10 +341,10 @@ sub input_prepare {
                        quit($self, 413);
                        return;
                }
-               input_tmpfile($input);
+               $input = input_tmpfile($self);
        } elsif (env_chunked($env)) {
                $len = CHUNK_START;
-               input_tmpfile($input);
+               $input = input_tmpfile($self);
        } else {
                $input = $null_io;
        }
@@ -450,10 +454,7 @@ sub quit {
 
 sub close {
        my $self = $_[0];
-       if (my $env = delete $self->{env}) {
-               delete $env->{'psgix.io'}; # prevent circular references
-       }
-       delete $self->{pull};
+       delete $self->{env}; # prevent circular references
        if (my $forward = delete $self->{forward}) {
                eval { $forward->close };
                err($self, "forward ->close error: $@") if $@;
@@ -467,11 +468,29 @@ sub busy () {
        ($self->{rbuf} || $self->{env} || $self->{wbuf});
 }
 
-# fires after pending writes are complete:
-sub restart_pass ($) {
-       $_[0]->{forward}->restart_read; # see PublicInbox::HTTPD::Async
+# Chunked and Identity packages are used for writing responses.
+# They may be exposed to the PSGI application when the PSGI app
+# returns a CODE ref for "push"-based responses
+package PublicInbox::HTTP::Chunked;
+use strict;
+
+sub write {
+       # ([$http], $buf) = @_;
+       PublicInbox::HTTP::chunked_write($_[0]->[0], $_[1])
+}
+
+sub close {
+       # $_[0] = [$http, $alive]
+       PublicInbox::HTTP::response_done(@{$_[0]});
 }
 
-sub enqueue_restart_pass ($) { $_[0]->write(\&restart_pass) }
+package PublicInbox::HTTP::Identity;
+use strict;
+our @ISA = qw(PublicInbox::HTTP::Chunked);
+
+sub write {
+       # ([$http], $buf) = @_;
+       PublicInbox::HTTP::identity_write($_[0]->[0], $_[1]);
+}
 
 1;