From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.3 required=3.0 tests=ALL_TRUSTED,AWL,BAYES_00, URIBL_BLOCKED shortcircuit=no autolearn=unavailable autolearn_force=no version=3.4.0 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id DEC151F99C for ; Mon, 23 May 2016 07:19:45 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH] http: chunk in the server, not middleware Date: Mon, 23 May 2016 07:19:45 +0000 Message-Id: <20160523071945.9761-1-e@80x24.org> List-Id: Since PSGI does not require Transfer-Encoding: chunked or Content-Length, we cannot expect random apps we host to chunk their responses. Thus, to improve interoperability, chunk at the HTTP layer like other PSGI servers do. I'm chosing a more syscall-intensive method (via multiple send(...MSG_MORE) for now to reduce copy + packet overhead. --- examples/public-inbox.psgi | 5 ----- lib/PublicInbox/HTTP.pm | 39 +++++++++++++++++++++++++++++++-------- script/public-inbox-httpd | 1 - t/git-http-backend.psgi | 1 - 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/examples/public-inbox.psgi b/examples/public-inbox.psgi index 876fc76..4edbf5e 100644 --- a/examples/public-inbox.psgi +++ b/examples/public-inbox.psgi @@ -12,11 +12,6 @@ PublicInbox::WWW->preload; use Plack::Builder; my $www = PublicInbox::WWW->new; builder { - # Chunked middleware conflicts with Starman: - # https://github.com/miyagawa/Starman/issues/23 - # However, it is strongly recommended to enable it if using - # public-inbox-httpd to allow persistent connections - # enable 'Chunked'; eval { enable 'Deflater', content_type => [ qw( diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm index 480800b..77b178c 100644 --- a/lib/PublicInbox/HTTP.pm +++ b/lib/PublicInbox/HTTP.pm @@ -180,12 +180,19 @@ sub response_header_write { my $conn = $env->{HTTP_CONNECTION} || ''; my $term = defined($len) || $chunked; - my $alive = $term && - (($proto eq 'HTTP/1.1' && $conn !~ /\bclose\b/i) || - ($conn =~ /\bkeep-alive\b/i)); - - $h .= 'Connection: ' . ($alive ? 'keep-alive' : 'close'); - $h .= "\r\nDate: " . http_date() . "\r\n\r\n"; + my $prot_persist = ($proto eq 'HTTP/1.1') && ($conn !~ /\bclose\b/i); + my $alive; + if (!$term && $prot_persist) { # auto-chunk + $chunked = $alive = 2; + $h .= "Transfer-Encoding: chunked\r\n"; + # no need for "Connection: keep-alive" with HTTP/1.1 + } elsif ($term && ($prot_persist || ($conn =~ /\bkeep-alive\b/i))) { + $alive = 1; + $h .= "Connection: keep-alive\r\n"; + } else { + $h .= "Connection: close\r\n"; + } + $h .= 'Date: ' . http_date() . "\r\n\r\n"; if (($len || $chunked) && $env->{REQUEST_METHOD} ne 'HEAD') { more($self, $h); @@ -195,13 +202,29 @@ sub response_header_write { $alive; } +# middlewares such as Deflater may write empty strings +sub chunked_wcb ($) { + my ($self) = @_; + sub { + return if $_[0] eq ''; + more($self, sprintf("%x\r\n", bytes::length($_[0]))); + more($self, $_[0]); + $self->write("\r\n"); + } +} + +sub identity_wcb ($) { + my ($self) = @_; + sub { $self->write(\($_[0])) if $_[0] ne '' } +} + sub response_write { my ($self, $env, $res) = @_; my $alive = response_header_write($self, $env, $res); - # middlewares such as Deflater may write empty strings - my $write = sub { $self->write(\($_[0])) if $_[0] ne '' }; + my $write = $alive == 2 ? chunked_wcb($self) : identity_wcb($self); my $close = sub { + $self->write("0\r\n\r\n") if $alive == 2; if ($alive) { $self->event_write; # watch for readability if done } else { diff --git a/script/public-inbox-httpd b/script/public-inbox-httpd index b29effc..f19582f 100755 --- a/script/public-inbox-httpd +++ b/script/public-inbox-httpd @@ -25,7 +25,6 @@ my $refresh = sub { PublicInbox::WWW->preload; my $www = PublicInbox::WWW->new; $app = builder { - enable 'Chunked'; eval { enable 'Deflater', content_type => [ qw( diff --git a/t/git-http-backend.psgi b/t/git-http-backend.psgi index 8cec7d3..c960714 100644 --- a/t/git-http-backend.psgi +++ b/t/git-http-backend.psgi @@ -11,7 +11,6 @@ use BSD::Resource qw(getrusage); my $git_dir = $ENV{GIANT_GIT_DIR} or die 'GIANT_GIT_DIR not defined in env'; my $git = PublicInbox::Git->new($git_dir); builder { - enable 'Chunked' if $ENV{TEST_CHUNK}; enable 'Head'; sub { my ($env) = @_;