]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/GitHTTPBackend.pm
git-http-backend: simplify dumb serving
[public-inbox.git] / lib / PublicInbox / GitHTTPBackend.pm
index 020b08869699af7cf9301d27b1282795cfaefd99..97d96d526121752060ff3274dbcdfee57cfebc31 100644 (file)
@@ -9,14 +9,7 @@ use warnings;
 use Fcntl qw(:seek);
 use IO::File;
 use PublicInbox::Spawn qw(spawn);
-
-# TODO: make configurable, but keep in mind it's better to have
-# multiple -httpd worker processes which are already scaled to
-# the proper number of CPUs and memory.  git-pack-objects(1) may
-# also use threads and bust memory limits, too, so I recommend
-# limiting threads to 1 (via `pack.threads` knob in git) for serving.
-my $LIMIT = 1;
-my $nr_running = 0;
+use HTTP::Date qw(time2str);
 
 # n.b. serving "description" and "cloneurl" should be innocuous enough to
 # not cause problems.  serving "config" might...
@@ -33,6 +26,10 @@ our $ANY = join('|', @binary, @text);
 my $BIN = join('|', @binary);
 my $TEXT = join('|', @text);
 
+my @no_cache = ('Expires', 'Fri, 01 Jan 1980 00:00:00 GMT',
+               'Pragma', 'no-cache',
+               'Cache-Control', 'no-cache, max-age=0, must-revalidate');
+
 my $nextq;
 sub do_next () {
        my $q = $nextq;
@@ -42,13 +39,13 @@ sub do_next () {
        }
 }
 
-sub r {
-       [ $_[0] , [qw(Content-Type text/plain Content-Length 0) ], [] ]
+sub r ($) {
+       my ($s) = @_;
+       [ $s, [qw(Content-Type text/plain Content-Length 0), @no_cache ], [] ]
 }
 
 sub serve {
        my ($cgi, $git, $path) = @_;
-       return serve_dumb($cgi, $git, $path) if $nr_running >= $LIMIT;
 
        my $service = $cgi->param('service') || '';
        if ($service =~ /\Agit-\w+-pack\z/ || $path =~ /\Agit-\w+-pack\z/) {
@@ -73,11 +70,15 @@ sub drop_client ($) {
 sub serve_dumb {
        my ($cgi, $git, $path) = @_;
 
+       my @h;
        my $type;
        if ($path =~ /\A(?:$BIN)\z/o) {
                $type = 'application/octet-stream';
+               push @h, 'Expires', time2str(time + 31536000);
+               push @h, 'Cache-Control', 'public, max-age=31536000';
        } elsif ($path =~ /\A(?:$TEXT)\z/o) {
                $type = 'text/plain';
+               push @h, @no_cache;
        } else {
                return r(404);
        }
@@ -91,43 +92,9 @@ sub serve_dumb {
        # TODO: If-Modified-Since and Last-Modified?
        open my $in, '<', $f or return r(404);
        my $len = $size;
-       my $n = 65536; # try to negotiate a big TCP window, first
-       my ($next, $fh);
-       my $cb = sub {
-               $n = $len if $len < $n;
-               my $r = sysread($in, my $buf, $n);
-               if (!defined $r) {
-                       err($env, "$f read error: $!");
-                       drop_client($env);
-               } elsif ($r <= 0) {
-                       err($env, "$f EOF with $len bytes left");
-                       drop_client($env);
-               } else {
-                       $len -= $r;
-                       $fh->write($buf);
-                       if ($len == 0) {
-                               $fh->close;
-                       } elsif ($next) {
-                               # avoid recursion in Danga::Socket::write
-                               unless ($nextq) {
-                                       $nextq = [];
-                                       Danga::Socket->AddTimer(0, *do_next);
-                               }
-                               # avoid buffering too much in case we have
-                               # slow clients:
-                               $n = 8192;
-                               push @$nextq, $next;
-                               return;
-                       }
-               }
-               # all done, cleanup references:
-               $fh = $next = undef;
-       };
-
        my $code = 200;
-       my @h = ('Content-Type', $type);
-       my $range = $env->{HTTP_RANGE};
-       if (defined $range && $range =~ /\bbytes=(\d*)-(\d*)\z/) {
+       push @h, 'Content-Type', $type;
+       if (($env->{HTTP_RANGE} || '') =~ /\bbytes=(\d*)-(\d*)\z/) {
                ($code, $len) = prepare_range($cgi, $in, \@h, $1, $2, $size);
                if ($code == 416) {
                        push @h, 'Content-Range', "bytes */$size";
@@ -135,18 +102,24 @@ sub serve_dumb {
                }
        }
        push @h, 'Content-Length', $len;
-
-       sub {
-               my ($res) = @_; # Plack callback
-               $fh = $res->([ $code, \@h ]);
-               if (defined $env->{'pi-httpd.async'}) {
-                       my $pi_http = $env->{'psgix.io'};
-                       $next = sub { $pi_http->write($cb) };
-                       $cb->(); # start it off!
-               } else {
-                       $cb->() while $fh;
-               }
-       }
+       my $n = 65536;
+       [ $code, \@h, Plack::Util::inline_object(close => sub { close $in },
+               getline => sub {
+                       return if $len == 0;
+                       $n = $len if $len < $n;
+                       my $r = sysread($in, my $buf, $n);
+                       if (!defined $r) {
+                               err($env, "$f read error: $!");
+                       } elsif ($r <= 0) {
+                               err($env, "$f EOF with $len bytes left");
+                       } else {
+                               $len -= $r;
+                               $n = 8192;
+                               return $buf;
+                       }
+                       drop_client($env);
+                       return;
+               })]
 }
 
 sub prepare_range {
@@ -233,7 +206,6 @@ sub serve_smart {
        $wpipe = $in = undef;
        $buf = '';
        my ($vin, $fh, $res);
-       $nr_running++;
 
        # Danga::Socket users, we queue up the read_enable callback to
        # fire after pending writes are complete:
@@ -254,15 +226,11 @@ sub serve_smart {
                        # PublicInbox::HTTPD::Async::close:
                        $rpipe->close;
                        $rpipe = undef;
-                       $nr_running--;
                }
                if (defined $pid) {
                        my $e = $pid == waitpid($pid, 0) ?
                                $? : "PID:$pid still running?";
-                       if ($e) {
-                               err($env, "git http-backend ($git_dir): $e");
-                               drop_client($env);
-                       }
+                       err($env, "git http-backend ($git_dir): $e") if $e;
                }
                return unless $res;
                my $dumb = serve_dumb($cgi, $git, $path);