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:
parent
d57ce99b7d
commit
50d85436f8
@ -113,6 +113,7 @@ module WEBrick
|
|||||||
@chunked = false
|
@chunked = false
|
||||||
@filename = nil
|
@filename = nil
|
||||||
@sent_size = 0
|
@sent_size = 0
|
||||||
|
@bodytempfile = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -253,7 +254,10 @@ module WEBrick
|
|||||||
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
||||||
@header.delete('content-length')
|
@header.delete('content-length')
|
||||||
elsif @header['content-length'].nil?
|
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
|
@header['content-length'] = (@body ? @body.bytesize : 0).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -282,6 +286,33 @@ module WEBrick
|
|||||||
end
|
end
|
||||||
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+
|
# Sends the headers on +socket+
|
||||||
|
|
||||||
@ -445,6 +476,7 @@ module WEBrick
|
|||||||
ensure
|
ensure
|
||||||
@body.close
|
@body.close
|
||||||
end
|
end
|
||||||
|
remove_body_tempfile
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_body_string(socket)
|
def send_body_string(socket)
|
||||||
@ -477,7 +509,12 @@ module WEBrick
|
|||||||
socket.write("0#{CRLF}#{CRLF}")
|
socket.write("0#{CRLF}#{CRLF}")
|
||||||
else
|
else
|
||||||
size = @header['content-length'].to_i
|
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
|
@sent_size = size
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -215,6 +215,46 @@ class TestWEBrickHTTPProxy < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
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)
|
def make_certificate(key, cn)
|
||||||
subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}")
|
subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}")
|
||||||
exts = [
|
exts = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user