diff options
author | Tanaka Akira <akr@fsij.org> | 2019-07-11 09:18:41 +0900 |
---|---|---|
committer | Tanaka Akira <akr@fsij.org> | 2019-07-11 09:18:41 +0900 |
commit | 50d85436f8f194aa78cd0e819471fe20767a5993 (patch) | |
tree | 18ce459623d72613567ace52ded44498b7a08c20 | |
parent | d57ce99b7d187eb3af7749a056edeea443da93f6 (diff) | |
download | ruby-50d85436f8f194aa78cd0e819471fe20767a5993.tar.gz |
WEBrick::HTTPResponse create tempfile if required.
WEBrick::HTTPProxyServer implementes HTTP proxy using
WEBrick and Net::HTTP.
WEBrick accepts HTTP/1.0 clients and
Net::HTTP uses always HTTP/1.1.
However HTTP/1.1 supports chunked transfer coding HTTP/1.0 doesn't.
Chunked transfer coding doesn't require that
content-length before the content is sent.
But non-chunked transfer coding require content-length before
the content is sent.
So, when HTTP/1.0 clients connects WEBrick::HTTPProxyServer and
origin server returns chunked response,
WEBrick::HTTPProxyServer needs to store whole content to
know the length of it.
This patch do it using tempfile.
-rw-r--r-- | lib/webrick/httpresponse.rb | 41 | ||||
-rw-r--r-- | test/webrick/test_httpproxy.rb | 40 |
2 files changed, 79 insertions, 2 deletions
diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb index f206a05ce9..62d8056cf7 100644 --- a/lib/webrick/httpresponse.rb +++ b/lib/webrick/httpresponse.rb @@ -113,6 +113,7 @@ module WEBrick @chunked = false @filename = nil @sent_size = 0 + @bodytempfile = nil end ## @@ -253,7 +254,10 @@ module WEBrick elsif %r{^multipart/byteranges} =~ @header['content-type'] @header.delete('content-length') elsif @header['content-length'].nil? - unless @body.is_a?(IO) + if @body.respond_to? :readpartial + elsif @body.respond_to? :call + make_body_tempfile + else @header['content-length'] = (@body ? @body.bytesize : 0).to_s end end @@ -282,6 +286,33 @@ module WEBrick end end + def make_body_tempfile # :nodoc: + return if @bodytempfile + bodytempfile = Tempfile.create("webrick") + if @body.nil? + # nothing + elsif @body.respond_to? :readpartial + IO.copy_stream(@body, bodytempfile) + @body.close + elsif @body.respond_to? :call + @body.call(bodytempfile) + else + bodytempfile.write @body + end + bodytempfile.rewind + @body = @bodytempfile = bodytempfile + @header['content-length'] = bodytempfile.stat.size.to_s + end + + def remove_body_tempfile # :nodoc: + if @bodytempfile + @bodytempfile.close + File.unlink @bodytempfile.path + @bodytempfile = nil + end + end + + ## # Sends the headers on +socket+ @@ -445,6 +476,7 @@ module WEBrick ensure @body.close end + remove_body_tempfile end def send_body_string(socket) @@ -477,7 +509,12 @@ module WEBrick socket.write("0#{CRLF}#{CRLF}") else size = @header['content-length'].to_i - @body.call(socket) + if @bodytempfile + @bodytempfile.rewind + IO.copy_stream(@bodytempfile, socket) + else + @body.call(socket) + end @sent_size = size end end diff --git a/test/webrick/test_httpproxy.rb b/test/webrick/test_httpproxy.rb index a9f6f7d610..8bc8a1240a 100644 --- a/test/webrick/test_httpproxy.rb +++ b/test/webrick/test_httpproxy.rb @@ -215,6 +215,46 @@ class TestWEBrickHTTPProxy < Test::Unit::TestCase end end + def test_http10_proxy_chunked + # Testing HTTP/1.0 client request and HTTP/1.1 chunked response + # from origin server. + # +------+ + # V | + # client -------> proxy ---+ + # GET GET + # HTTP/1.0 HTTP/1.1 + # non-chunked chunked + # + proxy_handler_called = request_handler_called = 0 + config = { + :ServerName => "localhost.localdomain", + :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 }, + :RequestCallback => Proc.new{|req, res| request_handler_called += 1 } + } + log_tester = lambda {|log, access_log| + log.reject! {|str| + %r{WARN chunked is set for an HTTP/1\.0 request\. \(ignored\)} =~ str + } + assert_equal([], log) + } + TestWEBrick.start_httpproxy(config, log_tester){|server, addr, port, log| + body = nil + server.mount_proc("/"){|req, res| + body = "#{req.request_method} #{req.path} #{req.body}" + res.chunked = true + res.body = -> (socket) { body.each_char {|c| socket.write c } } + } + http = Net::HTTP.new(addr, port, addr, port) + + # Don't use Net::HTTP because it uses HTTP/1.1. + TCPSocket.open(addr, port) {|s| + s.write "GET / HTTP/1.0\r\nHost: localhost.localdomain\r\n\r\n" + response = s.read + assert_equal(body, response[/.*\z/]) + } + } + end + def make_certificate(key, cn) subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}") exts = [ |