summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--NEWS11
-rw-r--r--lib/net/http.rb45
-rw-r--r--lib/net/protocol.rb2
-rw-r--r--test/net/http/test_http.rb81
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 <nahi@ruby-lang.org>
+
+ * 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 <drbrain@segment7.net>.
+
+ * test/net/http/test_http.rb: test it.
+
+ * NEWS: Add new feature.
+
Tue May 31 14:17:49 2011 NAKAMURA Usaku <usa@ruby-lang.org>
* 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