From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-2.9 required=3.0 tests=ALL_TRUSTED,AWL,BAYES_00, T_RP_MATCHES_RCVD shortcircuit=no autolearn=unavailable version=3.3.2 X-Original-To: yahns-public@yhbt.net Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 7B639200CE for ; Wed, 8 Apr 2015 06:16:49 +0000 (UTC) From: Eric Wong To: yahns-public@yhbt.net Subject: [PATCH] proxy_pass: support backends which rely on EOF to terminate Date: Wed, 8 Apr 2015 06:16:49 +0000 Message-Id: <1428473809-23316-1-git-send-email-e@80x24.org> List-Id: Not all backends are capable of generating chunked responses (especially not to HTTP/1.0 clients) nor can they generate the Content-Length (especially when gzipping), so they'll close the socket to signal EOF instead. --- lib/yahns/proxy_http_response.rb | 34 +++++++++++++++++++++++++++++++--- test/test_proxy_pass.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb index cbfc17e..af8d8cc 100644 --- a/lib/yahns/proxy_http_response.rb +++ b/lib/yahns/proxy_http_response.rb @@ -106,7 +106,7 @@ module Yahns::HttpResponse # :nodoc: return :wait_readable # self remains in :ignore, wait on upstream end until len == 0 - else # nasty chunked body + elsif kcar.chunked? # nasty chunked body req_res.proxy_trailers = nil # define to avoid warnings for now buf = '' case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf) @@ -137,6 +137,21 @@ module Yahns::HttpResponse # :nodoc: end # no loop here end wbuf = proxy_write(wbuf, trailer_out(tlr), alive) + + else # no Content-Length or Transfer-Encoding: chunked, wait on EOF! + + case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf) + when String + wbuf = proxy_write(wbuf, tmp, alive) + when nil + req_res.shutdown + break + when :wait_readable + # for ensure: + wbuf ||= Yahns::Wbuf.new(nil, alive, k.output_buffer_tmpdir, false) + return :wait_readable # self remains in :ignore, wait on upstream + end while true + end end @@ -163,7 +178,7 @@ module Yahns::HttpResponse # :nodoc: return :wait_readable # self remains in :ignore, wait on upstream end while len != 0 - else # nasty chunked body + elsif kcar.chunked? # nasty chunked body buf = '' unless req_res.proxy_trailers @@ -193,10 +208,23 @@ module Yahns::HttpResponse # :nodoc: end # no loop here end wbuf.wbuf_write(self, trailer_out(tlr)) + + else # no Content-Length or Transfer-Encoding: chunked, wait on EOF! + + case tmp = req_res.kgio_tryread(0x2000, rbuf) + when String + wbuf.wbuf_write(self, tmp) + when nil + req_res.shutdown + break + when :wait_readable + return :wait_readable # self remains in :ignore, wait on upstream + end while true + end busy = wbuf.busy and return proxy_busy_mod_blocked(wbuf, busy) - proxy_busy_mod_done(wbuf.wbuf_persist) + proxy_busy_mod_done(wbuf.wbuf_persist) # returns nil end def proxy_busy_mod_done(alive) diff --git a/test/test_proxy_pass.rb b/test/test_proxy_pass.rb index e4e97d1..eb866b3 100644 --- a/test/test_proxy_pass.rb +++ b/test/test_proxy_pass.rb @@ -65,6 +65,16 @@ class TestProxyPass < Testcase io = env['rack.hijack'].call io.write(TRUNCATE_BODY) io.close + when '/eof-body-fast' + io = env['rack.hijack'].call + io.write("HTTP/1.0 200 OK\r\n\r\neof-body-fast") + io.close + when '/eof-body-slow' + io = env['rack.hijack'].call + io.write("HTTP/1.0 200 OK\r\n\r\n") + sleep 0.1 + io.write("eof-body-slow") + io.close when '/truncate-head' io = env['rack.hijack'].call io.write(TRUNCATE_HEAD) @@ -244,6 +254,7 @@ class TestProxyPass < Testcase end end + check_eof_body(host, port) check_pipelining(host, port) check_response_trailer(host, port) @@ -531,4 +542,22 @@ class TestProxyPass < Testcase end thrs.each { |t| assert_equal(:OK, t.value) } end + + def check_eof_body(host, port) + Timeout.timeout(60) do + s = TCPSocket.new(host, port) + s.write("GET /eof-body-fast HTTP/1.0\r\n\r\n") + res = s.read + assert_match %r{\AHTTP/1\.1 200 OK\r\n}, res + assert_match %r{\r\n\r\neof-body-fast\z}, res + s.close + + s = TCPSocket.new(host, port) + s.write("GET /eof-body-slow HTTP/1.0\r\n\r\n") + res = s.read + assert_match %r{\AHTTP/1\.1 200 OK\r\n}, res + assert_match %r{\r\n\r\neof-body-slow\z}, res + s.close + end + end end -- EW