mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Introduce write_timeout to Net::HTTP [Feature #13396]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@63587 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
acc48fce30
commit
bd7c46a7aa
5 changed files with 169 additions and 5 deletions
9
NEWS
9
NEWS
|
@ -150,6 +150,15 @@ with all sufficient information, see the ChangeLog file or Redmine
|
||||||
|
|
||||||
* Matrix#antisymmetric?
|
* Matrix#antisymmetric?
|
||||||
|
|
||||||
|
* Net
|
||||||
|
|
||||||
|
* New method:
|
||||||
|
|
||||||
|
* Add write_timeout keyword argument to Net::BufferedIO.new. [Feature #13396]
|
||||||
|
|
||||||
|
* Add Net::BufferedIO#write_timeout, Net::BufferedIO#write_timeout=,
|
||||||
|
Net::HTTP#write_timeout, and Net::HTTP#write_timeout=. [Feature #13396]
|
||||||
|
|
||||||
* REXML
|
* REXML
|
||||||
|
|
||||||
* Improved some XPath implementations:
|
* Improved some XPath implementations:
|
||||||
|
|
|
@ -575,7 +575,7 @@ module Net #:nodoc:
|
||||||
#
|
#
|
||||||
# _opt_ sets following values by its accessor.
|
# _opt_ sets following values by its accessor.
|
||||||
# The keys are ca_file, ca_path, cert, cert_store, ciphers,
|
# The keys are ca_file, ca_path, cert, cert_store, ciphers,
|
||||||
# close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout,
|
# close_on_empty_response, key, open_timeout, read_timeout, write_timeout, ssl_timeout,
|
||||||
# ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
|
# ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
|
||||||
# If you set :use_ssl as true, you can use https and default value of
|
# If you set :use_ssl as true, you can use https and default value of
|
||||||
# verify_mode is set as OpenSSL::SSL::VERIFY_PEER.
|
# verify_mode is set as OpenSSL::SSL::VERIFY_PEER.
|
||||||
|
@ -673,6 +673,7 @@ module Net #:nodoc:
|
||||||
@started = false
|
@started = false
|
||||||
@open_timeout = 60
|
@open_timeout = 60
|
||||||
@read_timeout = 60
|
@read_timeout = 60
|
||||||
|
@write_timeout = 60
|
||||||
@continue_timeout = nil
|
@continue_timeout = nil
|
||||||
@max_retries = 1
|
@max_retries = 1
|
||||||
@debug_output = nil
|
@debug_output = nil
|
||||||
|
@ -741,6 +742,12 @@ module Net #:nodoc:
|
||||||
# it raises a Net::ReadTimeout exception. The default value is 60 seconds.
|
# it raises a Net::ReadTimeout exception. The default value is 60 seconds.
|
||||||
attr_reader :read_timeout
|
attr_reader :read_timeout
|
||||||
|
|
||||||
|
# Number of seconds to wait for one block to be write (via one write(2)
|
||||||
|
# call). Any number may be used, including Floats for fractional
|
||||||
|
# seconds. If the HTTP object cannot write data in this many seconds,
|
||||||
|
# it raises a Net::WriteTimeout exception. The default value is 60 seconds.
|
||||||
|
attr_reader :write_timeout
|
||||||
|
|
||||||
# Maximum number of times to retry an idempotent request in case of
|
# Maximum number of times to retry an idempotent request in case of
|
||||||
# Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET,
|
# Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET,
|
||||||
# Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError,
|
# Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError,
|
||||||
|
@ -763,6 +770,12 @@ module Net #:nodoc:
|
||||||
@read_timeout = sec
|
@read_timeout = sec
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Setter for the write_timeout attribute.
|
||||||
|
def write_timeout=(sec)
|
||||||
|
@socket.write_timeout = sec if @socket
|
||||||
|
@write_timeout = sec
|
||||||
|
end
|
||||||
|
|
||||||
# Seconds to wait for 100 Continue response. If the HTTP object does not
|
# Seconds to wait for 100 Continue response. If the HTTP object does not
|
||||||
# receive a response in this many seconds it sends the request body. The
|
# receive a response in this many seconds it sends the request body. The
|
||||||
# default value is +nil+.
|
# default value is +nil+.
|
||||||
|
@ -944,6 +957,7 @@ module Net #:nodoc:
|
||||||
if use_ssl?
|
if use_ssl?
|
||||||
if proxy?
|
if proxy?
|
||||||
plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
|
plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
|
||||||
|
write_timeout: @write_timeout,
|
||||||
continue_timeout: @continue_timeout,
|
continue_timeout: @continue_timeout,
|
||||||
debug_output: @debug_output)
|
debug_output: @debug_output)
|
||||||
buf = "CONNECT #{@address}:#{@port} HTTP/#{HTTPVersion}\r\n"
|
buf = "CONNECT #{@address}:#{@port} HTTP/#{HTTPVersion}\r\n"
|
||||||
|
@ -985,6 +999,7 @@ module Net #:nodoc:
|
||||||
D "SSL established"
|
D "SSL established"
|
||||||
end
|
end
|
||||||
@socket = BufferedIO.new(s, read_timeout: @read_timeout,
|
@socket = BufferedIO.new(s, read_timeout: @read_timeout,
|
||||||
|
write_timeout: @write_timeout,
|
||||||
continue_timeout: @continue_timeout,
|
continue_timeout: @continue_timeout,
|
||||||
debug_output: @debug_output)
|
debug_output: @debug_output)
|
||||||
on_connect
|
on_connect
|
||||||
|
|
|
@ -77,11 +77,18 @@ module Net # :nodoc:
|
||||||
|
|
||||||
class ReadTimeout < Timeout::Error; end
|
class ReadTimeout < Timeout::Error; end
|
||||||
|
|
||||||
|
##
|
||||||
|
# WriteTimeout, a subclass of Timeout::Error, is raised if a chunk of the
|
||||||
|
# response cannot be read within the read_timeout.
|
||||||
|
|
||||||
|
class WriteTimeout < Timeout::Error; end
|
||||||
|
|
||||||
|
|
||||||
class BufferedIO #:nodoc: internal use only
|
class BufferedIO #:nodoc: internal use only
|
||||||
def initialize(io, read_timeout: 60, continue_timeout: nil, debug_output: nil)
|
def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil)
|
||||||
@io = io
|
@io = io
|
||||||
@read_timeout = read_timeout
|
@read_timeout = read_timeout
|
||||||
|
@write_timeout = write_timeout
|
||||||
@continue_timeout = continue_timeout
|
@continue_timeout = continue_timeout
|
||||||
@debug_output = debug_output
|
@debug_output = debug_output
|
||||||
@rbuf = ''.b
|
@rbuf = ''.b
|
||||||
|
@ -89,6 +96,7 @@ module Net # :nodoc:
|
||||||
|
|
||||||
attr_reader :io
|
attr_reader :io
|
||||||
attr_accessor :read_timeout
|
attr_accessor :read_timeout
|
||||||
|
attr_accessor :write_timeout
|
||||||
attr_accessor :continue_timeout
|
attr_accessor :continue_timeout
|
||||||
attr_accessor :debug_output
|
attr_accessor :debug_output
|
||||||
|
|
||||||
|
@ -237,9 +245,32 @@ module Net # :nodoc:
|
||||||
|
|
||||||
def write0(*strs)
|
def write0(*strs)
|
||||||
@debug_output << strs.map(&:dump).join if @debug_output
|
@debug_output << strs.map(&:dump).join if @debug_output
|
||||||
len = @io.write(*strs)
|
case len = @io.write_nonblock(*strs, exception: false)
|
||||||
@written_bytes += len
|
when Integer
|
||||||
len
|
orig_len = len
|
||||||
|
strs.each_with_index do |str, i|
|
||||||
|
@written_bytes += str.bytesize
|
||||||
|
len -= str.bytesize
|
||||||
|
if len == 0
|
||||||
|
if strs.size == i+1
|
||||||
|
return orig_len
|
||||||
|
else
|
||||||
|
strs = strs[i+1..] # rest
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elsif len < 0
|
||||||
|
strs = strs[i..] # str and rest
|
||||||
|
strs[0] = str[len, -len]
|
||||||
|
break
|
||||||
|
else # len > 0
|
||||||
|
# next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# continue looping
|
||||||
|
when :wait_writable
|
||||||
|
@io.to_io.wait_writable(@write_timeout) or raise Net::WriteTimeout
|
||||||
|
# continue looping
|
||||||
|
end while true
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -529,6 +529,30 @@ module TestNetHTTP_version_1_1_methods
|
||||||
assert_equal data, res.entity
|
assert_equal data, res.entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_timeout_during_HTTP_session_write
|
||||||
|
bug4246 = "expected the HTTP session to have timed out but have not. c.f. [ruby-core:34203]"
|
||||||
|
|
||||||
|
th = nil
|
||||||
|
# listen for connections... but deliberately do not read
|
||||||
|
TCPServer.open('localhost', 0) {|server|
|
||||||
|
port = server.addr[1]
|
||||||
|
|
||||||
|
conn = Net::HTTP.new('localhost', port)
|
||||||
|
conn.write_timeout = 0.01
|
||||||
|
conn.open_timeout = 0.1
|
||||||
|
|
||||||
|
th = Thread.new do
|
||||||
|
assert_raise(Net::WriteTimeout) {
|
||||||
|
conn.post('/', "a"*5_000_000)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
assert th.join(10), bug4246
|
||||||
|
}
|
||||||
|
ensure
|
||||||
|
th.kill
|
||||||
|
th.join
|
||||||
|
end
|
||||||
|
|
||||||
def test_timeout_during_HTTP_session
|
def test_timeout_during_HTTP_session
|
||||||
bug4246 = "expected the HTTP session to have timed out but have not. c.f. [ruby-core:34203]"
|
bug4246 = "expected the HTTP session to have timed out but have not. c.f. [ruby-core:34203]"
|
||||||
|
|
||||||
|
|
|
@ -26,4 +26,89 @@ class TestProtocol < Test::Unit::TestCase
|
||||||
assert_equal("\u3042\r\n.\r\n", sio.string)
|
assert_equal("\u3042\r\n.\r\n", sio.string)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_mockio
|
||||||
|
mockio = Object.new
|
||||||
|
mockio.instance_variable_set(:@str, +'')
|
||||||
|
mockio.instance_variable_set(:@capacity, 100)
|
||||||
|
def mockio.string; @str; end
|
||||||
|
def mockio.to_io; self; end
|
||||||
|
def mockio.wait_writable(sec); sleep sec; false; end
|
||||||
|
def mockio.write_nonblock(*strs, exception: true)
|
||||||
|
if @capacity <= @str.bytesize
|
||||||
|
if exception
|
||||||
|
raise Net::WaitWritable
|
||||||
|
else
|
||||||
|
return :wait_writable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
len = 0
|
||||||
|
strs.each do |str|
|
||||||
|
len1 = @str.bytesize
|
||||||
|
break if @capacity <= len1
|
||||||
|
@str << str[0, @capacity - @str.bytesize]
|
||||||
|
len2 = @str.bytesize
|
||||||
|
len += len2 - len1
|
||||||
|
end
|
||||||
|
len
|
||||||
|
end
|
||||||
|
mockio
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_timeout
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
assert_raise(Net::WriteTimeout){ io.write("a"*1000) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_success
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
len = io.write("a"*10)
|
||||||
|
assert_equal "a"*10, mockio.string
|
||||||
|
assert_equal 10, len
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_success2
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
len = io.write("a"*100)
|
||||||
|
assert_equal "a"*100, mockio.string
|
||||||
|
assert_equal 100, len
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_success_multi1
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
len = io.write("a"*50, "a"*49)
|
||||||
|
assert_equal "a"*99, mockio.string
|
||||||
|
assert_equal 99, len
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_success_multi2
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
len = io.write("a"*50, "a"*50)
|
||||||
|
assert_equal "a"*100, mockio.string
|
||||||
|
assert_equal 100, len
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_timeout_multi1
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
assert_raise(Net::WriteTimeout){ io.write("a"*50,"a"*51) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_write0_timeout_multi2
|
||||||
|
mockio = create_mockio
|
||||||
|
io = Net::BufferedIO.new(mockio)
|
||||||
|
io.write_timeout = 0.1
|
||||||
|
assert_raise(Net::WriteTimeout){ io.write("a"*50,"a"*50,"a") }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue