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.
This commit is contained in:
Tanaka Akira 2019-07-11 09:18:41 +09:00
Родитель d57ce99b7d
Коммит 50d85436f8
2 изменённых файлов: 79 добавлений и 2 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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 = [