From b219a56c07ad5d9fb1f6cddfb78e97eff1e89379 Mon Sep 17 00:00:00 2001 From: nahi Date: Tue, 31 May 2011 08:10:42 +0000 Subject: * lib/net/http.rb, lib/net/protocol.rb: Allow to configure to wait server returning '100 continue' response befor sending HTTP request body. See NEWS for more detail. See #3622. Original patch is made by Eric Hodel . * test/net/http/test_http.rb: test it. * NEWS: Add new feature. On my env (Ubuntu 11.04 64bit), 9510 tests, 2203824 assertions, 0 failures, 0 errors, 29 skips -> 9514 tests, 2203836 assertions, 0 failures, 0 errors, 29 skips git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@31860 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 11 +++++++ NEWS | 11 +++++++ lib/net/http.rb | 45 ++++++++++++++++++++++---- lib/net/protocol.rb | 2 ++ test/net/http/test_http.rb | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 484fb7eccd..4bb5c1534f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +Tue May 31 17:03:24 2011 Hiroshi Nakamura + + * lib/net/http.rb, lib/net/protocol.rb: Allow to configure to wait + server returning '100 continue' response befor sending HTTP request + body. See NEWS for more detail. See #3622. + Original patch is made by Eric Hodel . + + * test/net/http/test_http.rb: test it. + + * NEWS: Add new feature. + Tue May 31 14:17:49 2011 NAKAMURA Usaku * io.c (rb_io_s_pipe): potential bug. the mode of read IO is set as diff --git a/NEWS b/NEWS index 41df6f5490..49cd303d46 100644 --- a/NEWS +++ b/NEWS @@ -98,6 +98,17 @@ with all sufficient information, see the ChangeLog file. * net/http * SNI (Server Name Indication) supported for HTTPS. + * Allow to configure to wait server returning '100 continue' response + before sending HTTP request body. Set Net::HTTP#continue_timeout AND pass + 'expect' => '100-continue' to a extra HTTP header. + + For example, the following code sends HTTP header and waits for getting + '100 continue' response before sending HTTP request body. When 0.5 [sec] + timeout occurs or the server send '100 continue', the client sends HTTP + request body. + http.continue_timeout = 0.5 + http.request_post('/continue', 'body=BODY', 'expect' => '100-continue') + * openssl * PKey::RSA and PKey::DSA now use the generic X.509 encoding scheme (e.g. used in a X.509 certificate's Subject Public Key Info) when diff --git a/lib/net/http.rb b/lib/net/http.rb index 4faf6c87ae..9a99338d50 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -581,6 +581,7 @@ module Net #:nodoc: @started = false @open_timeout = nil @read_timeout = 60 + @continue_timeout = nil @debug_output = nil @use_ssl = false @ssl_context = nil @@ -634,6 +635,16 @@ module Net #:nodoc: @read_timeout = sec end + # 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. + attr_reader :continue_timeout + + # Setter for the continue_timeout attribute. + def continue_timeout=(sec) + @socket.continue_timeout = sec if @socket + @continue_timeout = sec + end + # Returns true if the HTTP session has been started. def started? @started @@ -764,6 +775,7 @@ module Net #:nodoc: end @socket = BufferedIO.new(s) @socket.read_timeout = @read_timeout + @socket.continue_timeout = @continue_timeout @socket.debug_output = @debug_output if use_ssl? begin @@ -1298,12 +1310,15 @@ module Net #:nodoc: def transport_request(req) begin_transport req - req.exec @socket, @curr_http_version, edit_path(req.path) - begin - res = HTTPResponse.read_new(@socket) - end while res.kind_of?(HTTPContinue) - res.reading_body(@socket, req.response_body_permitted?) { - yield res if block_given? + res = catch(:response) { + req.exec @socket, @curr_http_version, edit_path(req.path) + begin + res = HTTPResponse.read_new(@socket) + end while res.kind_of?(HTTPContinue) + res.reading_body(@socket, req.response_body_permitted?) { + yield res if block_given? + } + res } end_transport req, res res @@ -1915,6 +1930,7 @@ module Net #:nodoc: delete 'Transfer-Encoding' supply_default_content_type write_header sock, ver, path + wait_for_continue sock, ver if sock.continue_timeout sock.write body end @@ -1925,6 +1941,7 @@ module Net #:nodoc: end supply_default_content_type write_header sock, ver, path + wait_for_continue sock, ver if sock.continue_timeout if chunked? while s = f.read(1024) sock.write(sprintf("%x\r\n", s.length) << s << "\r\n") @@ -2030,6 +2047,22 @@ module Net #:nodoc: set_content_type 'application/x-www-form-urlencoded' end + ## + # Waits up to the continue timeout for a response from the server provided + # we're speaking HTTP 1.1 and are expecting a 100-continue response. + + def wait_for_continue(sock, ver) + if ver >= '1.1' and @header['expect'] and + @header['expect'].include?('100-continue') + if IO.select([sock.io], nil, nil, sock.continue_timeout) + res = HTTPResponse.read_new(sock) + unless res.kind_of?(Net::HTTPContinue) + throw :response, res + end + end + end + end + def write_header(sock, ver, path) buf = "#{@method} #{path} HTTP/#{ver}\r\n" each_capitalized do |k,v| diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index f908f3a1d6..ab0e70e609 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -50,12 +50,14 @@ module Net # :nodoc: def initialize(io) @io = io @read_timeout = 60 + @continue_timeout = nil @debug_output = nil @rbuf = '' end attr_reader :io attr_accessor :read_timeout + attr_accessor :continue_timeout attr_accessor :debug_output def inspect diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 18ca79e721..9a7a149702 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -464,3 +464,84 @@ class TestNetHTTP_v1_2_chunked < Test::Unit::TestCase } end end + +class TestNetHTTPContinue < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'port' => 10081, + 'proxy_host' => nil, + 'proxy_port' => nil, + 'chunked' => true, + } + + include TestNetHTTPUtils + + def logfile + @debug = StringIO.new('') + end + + def mount_proc(&block) + @server.mount('/continue', WEBrick::HTTPServlet::ProcHandler.new(block.to_proc)) + end + + def test_expect_continue + mount_proc {|req, res| + req.continue + res.body = req.query['body'] + } + start {|http| + http.continue_timeout = 0.2 + http.request_post('/continue', 'body=BODY', 'expect' => '100-continue') {|res| + assert_equal('BODY', res.read_body) + } + } + assert_match(/Expect: 100-continue/, @debug.string) + assert_match(/HTTP\/1.1 100 continue/, @debug.string) + end + + def test_expect_continue_timeout + mount_proc {|req, res| + sleep 0.2 + req.continue # just ignored because it's '100' + res.body = req.query['body'] + } + start {|http| + http.continue_timeout = 0 + http.request_post('/continue', 'body=BODY', 'expect' => '100-continue') {|res| + assert_equal('BODY', res.read_body) + } + } + assert_match(/Expect: 100-continue/, @debug.string) + assert_match(/HTTP\/1.1 100 continue/, @debug.string) + end + + def test_expect_continue_error + mount_proc {|req, res| + res.status = 501 + res.body = req.query['body'] + } + start {|http| + http.continue_timeout = 0 + http.request_post('/continue', 'body=ERROR', 'expect' => '100-continue') {|res| + assert_equal('ERROR', res.read_body) + } + } + assert_match(/Expect: 100-continue/, @debug.string) + assert_not_match(/HTTP\/1.1 100 continue/, @debug.string) + end + + def test_expect_continue_error_while_waiting + mount_proc {|req, res| + res.status = 501 + res.body = req.query['body'] + } + start {|http| + http.continue_timeout = 0.5 + http.request_post('/continue', 'body=ERROR', 'expect' => '100-continue') {|res| + assert_equal('ERROR', res.read_body) + } + } + assert_match(/Expect: 100-continue/, @debug.string) + assert_not_match(/HTTP\/1.1 100 continue/, @debug.string) + end +end -- cgit v1.2.3