]> Sergey Matveev's repositories - public-inbox.git/commitdiff
qspawn: learn to gzip streaming responses
authorEric Wong <e@yhbt.net>
Sun, 5 Jul 2020 23:27:28 +0000 (23:27 +0000)
committerEric Wong <e@yhbt.net>
Mon, 6 Jul 2020 20:01:15 +0000 (20:01 +0000)
This will allow us to gzip responses generated by cgit
and any other CGI programs or long-lived streaming
responses we may spawn.

lib/PublicInbox/GzipFilter.pm
lib/PublicInbox/Qspawn.pm
t/httpd-corner.psgi
t/httpd-corner.t

index 0fbb4476aabf637d82af5f5f3abf2ff47de1976c..0a6c56a5da9beb27fa0c88356e379c42fb35853d 100644 (file)
@@ -32,6 +32,22 @@ sub gzf_maybe ($$) {
        bless { gz => $gz }, __PACKAGE__;
 }
 
+sub qsp_maybe ($$) {
+       my ($res_hdr, $env) = @_;
+       return if ($env->{HTTP_ACCEPT_ENCODING} // '') !~ /\bgzip\b/;
+       my $hdr = join("\n", @$res_hdr);
+       return if $hdr !~ m!^Content-Type\n
+                               (?:(?:text/(?:html|plain))|
+                               application/atom\+xml)\b!ixsm;
+       return if $hdr =~ m!^Content-Encoding\ngzip\n!smi;
+       return if $hdr =~ m!^Content-Length\n[0-9]+\n!smi;
+       return if $hdr =~ m!^Transfer-Encoding\n!smi;
+       # in case Plack::Middleware::Deflater is loaded:
+       return if $env->{'plack.skip-deflater'}++;
+       push @$res_hdr, @GZIP_HDRS;
+       bless {}, __PACKAGE__;
+}
+
 sub gzip_or_die () {
        my ($gz, $err) = Compress::Raw::Zlib::Deflate->new(%OPT);
        $err == Z_OK or die "Deflate->new failed: $err";
index d395a10b3b156b1fac05facbad8a4eca04ef44c2..88b6d390a0721e529f31f9826144dad641aa067c 100644 (file)
@@ -25,8 +25,8 @@
 
 package PublicInbox::Qspawn;
 use strict;
-use warnings;
 use PublicInbox::Spawn qw(popen_rd);
+use PublicInbox::GzipFilter;
 
 # n.b.: we get EAGAIN with public-inbox-httpd, and EINTR on other PSGI servers
 use Errno qw(EAGAIN EINTR);
@@ -255,7 +255,9 @@ sub psgi_return_init_cb {
        my ($self) = @_;
        my $r = rd_hdr($self) or return;
        my $env = $self->{psgi_env};
-       my $filter = delete $env->{'qspawn.filter'};
+       my $filter = delete $env->{'qspawn.filter'} //
+               PublicInbox::GzipFilter::qsp_maybe($r->[1], $env);
+
        my $wcb = delete $env->{'qspawn.wcb'};
        my $async = delete $self->{async};
        if (scalar(@$r) == 3) { # error
index 44629620051eb52424675545f506d1691306ef2a..cb41cfa0512d059a00382bc6683c3f0982bab80b 100644 (file)
@@ -94,6 +94,13 @@ my $app = sub {
                return $qsp->psgi_return($env, undef, sub {
                        [ 200, [ qw(Content-Type application/octet-stream)]]
                });
+       } elsif ($path eq '/psgi-return-compressible') {
+               require PublicInbox::Qspawn;
+               my $cmd = [qw(echo goodbye world)];
+               my $qsp = PublicInbox::Qspawn->new($cmd);
+               return $qsp->psgi_return($env, undef, sub {
+                       [200, [qw(Content-Type text/plain)]]
+               });
        } elsif ($path eq '/psgi-return-enoent') {
                require PublicInbox::Qspawn;
                my $cmd = [ 'this-better-not-exist-in-PATH'.rand ];
index 681486550688222d82fa0e8adbcd61256fb72527..514672a1b2aadaacd48e9f5f61f2f69d589bcdff 100644 (file)
@@ -340,11 +340,18 @@ SKIP: {
        is($n, 30 * 1024 * 1024, 'got expected output from curl');
        is($non_zero, 0, 'read all zeros');
 
-       require_mods(@zmods, 2);
+       require_mods(@zmods, 4);
        my $buf = xqx([$curl, '-sS', "$base/psgi-return-gzip"]);
        is($?, 0, 'curl succesful');
        IO::Uncompress::Gunzip::gunzip(\$buf => \(my $out));
        is($out, "hello world\n");
+       my $curl_rdr = { 2 => \(my $curl_err = '') };
+       $buf = xqx([$curl, qw(-sSv --compressed),
+                       "$base/psgi-return-compressible"], undef, $curl_rdr);
+       is($?, 0, 'curl --compressed successful');
+       is($buf, "goodbye world\n", 'gzipped response as expected');
+       like($curl_err, qr/\bContent-Encoding: gzip\b/,
+               'curl got gzipped response');
 }
 
 {