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 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 159B91FA78 for ; Sat, 4 Apr 2015 01:08:21 +0000 (UTC) From: Eric Wong To: yahns-public@yhbt.net Subject: [PATCH 4/4] proxy_pass: more tests for giant headers and truncations Date: Sat, 4 Apr 2015 01:08:16 +0000 Message-Id: <1428109696-14564-5-git-send-email-e@80x24.org> In-Reply-To: <1428109696-14564-1-git-send-email-e@80x24.org> References: <1428109696-14564-1-git-send-email-e@80x24.org> List-Id: We need to ensure more uncommon cases such as gigantic upstream headers and truncated upstream responses are handled properly and predictably. --- lib/yahns/proxy_http_response.rb | 9 ++++-- test/test_proxy_pass.rb | 62 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb index 31505d3..8f4790e 100644 --- a/lib/yahns/proxy_http_response.rb +++ b/lib/yahns/proxy_http_response.rb @@ -28,10 +28,13 @@ def proxy_write(wbuf, buf, alive) def proxy_err_response(code, req_res, exc, wbuf) logger = @hs.env['rack.logger'] - if exc - Yahns::Log.exception(logger, 'upstream error', exc) - else + case exc + when nil logger.error('premature upstream EOF') + when Kcar::ParserError + logger.error("upstream response error: #{exc.message}") + else + Yahns::Log.exception(logger, 'upstream error', exc) end # try to write something, but don't care if we fail Integer === code and diff --git a/test/test_proxy_pass.rb b/test/test_proxy_pass.rb index 163a0f7..47fc231 100644 --- a/test/test_proxy_pass.rb +++ b/test/test_proxy_pass.rb @@ -13,6 +13,11 @@ class TestProxyPass < Testcase TRUNCATE_HEAD = "HTTP/1.1 200 OK\r\n" \ "Content-Length: 666\r\n".freeze + # not too big, or kcar will reject + BIG_HEADER = [%w(Content-Type text/plain), %W(Content-Length #{OMFG.size})] + 3000.times { |i| BIG_HEADER << %W(X-#{i} BIG-HEADER!!!!!!!!!!!!!!) } + BIG_HEADER.freeze + class ProxiedApp def call(env) h = [ %w(Content-Length 3), %w(Content-Type text/plain) ] @@ -22,6 +27,11 @@ def call(env) when '/giant-body' h = [ %W(Content-Length #{OMFG.size}), %w(Content-Type text/plain) ] [ 200, h, [ OMFG ] ] + when '/big-headers' + [ 200, BIG_HEADER, [ OMFG ] ] + when '/oversize-headers' + 100000.times { |x| h << %W(X-TOOBIG-#{x} #{x}) } + [ 200, h, [ "big" ] ] when %r{\A/slow-headers-(\d+(?:\.\d+)?)\z} delay = $1.to_f io = env['rack.hijack'].call @@ -61,7 +71,12 @@ def chunky.each [ 200, h, [ "hi\n"] ] end when 'HEAD' - [ 200, h, [] ] + case env['PATH_INFO'] + when '/big-headers' + [ 200, BIG_HEADER, [] ] + else + [ 200, h, [] ] + end when 'PUT' buf = env['rack.input'].read [ 201, { @@ -158,7 +173,7 @@ def test_proxy_pass @srv2.close cfg.instance_eval do app(:rack, Yahns::ProxyPass.new("http://#{host2}:#{port2}")) do - listen "#{host}:#{port}" + listen "#{host}:#{port}", sndbuf: 16384 client_max_body_size nil end stderr_path err.path @@ -245,6 +260,14 @@ def test_proxy_pass res = http.request(req) assert_equal OMFG, res.body assert_equal 201, res.code.to_i + + # sometimes upstream feeds kcar too much + req = Net::HTTP::Get.new('/oversize-headers') + res = http.request(req) + errs = File.readlines(@err.path).grep(/ERROR/) + assert_equal 1, errs.size + assert_match %r{upstream response error:}, errs[0] + @err.truncate(0) end # ensure we do not chunk responses back to an HTTP/1.0 client even if @@ -264,6 +287,8 @@ def test_proxy_pass end end check_truncated_upstream(host, port) + check_slow_giant_body(host, port) + check_slow_read_headers(host, port) ensure gplv3.close if gplv3 quit_wait pid @@ -376,4 +401,37 @@ def check_truncated_upstream(host, port) @err.truncate(0) end end + + def check_slow_giant_body(host, port) + s = TCPSocket.new(host, port) + s.write "GET /giant-body HTTP/1.0\r\n\r\n" + sleep 0.1 + str = '' + buf = '' + assert_raises(EOFError) { loop { str << s.readpartial(400, buf) } } + h, b = str.split(/\r\n\r\n/, 2) + assert_equal OMFG, b + assert_match %r{\AHTTP/1\.1 200\b}, h + ensure + s.close if s + end + + def check_slow_read_headers(host, port) + s = TCPSocket.new(host, port) + s.write "GET /big-headers HTTP/1.1\r\nHost: example.com\r\n\r\n" + s.write "HEAD /big-headers HTTP/1.0\r\n\r\n" + buf = '' + res = '' + sleep 0.1 + begin + res << s.readpartial(32786, buf) + rescue EOFError + break + end while true + # res = Timeout.timeout(60) { s.read } + assert_match %r{\r\n\r\n\z}, res + assert_match %r{\AHTTP/1\.1 200 OK}, res + ensure + s.close if s + end end -- EW