Index: lib/net/http/response.rb =================================================================== --- lib/net/http/response.rb (revision 35950) +++ lib/net/http/response.rb (working copy) @@ -132,6 +132,7 @@ class Net::HTTPResponse initialize_http_header nil @body = nil @read = false + @uri = nil end # The HTTP version supported by the server. @@ -146,6 +147,10 @@ class Net::HTTPResponse attr_reader :message alias msg message # :nodoc: obsolete + # The URI used to fetch this response. The response URI is only available + # if a URI was used to create the request. + attr_reader :uri + def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end @@ -171,6 +176,10 @@ class Net::HTTPResponse error! unless self.kind_of?(Net::HTTPSuccess) end + def uri= uri # :nodoc: + @uri = uri.dup if uri + end + # # header (for backward compatibility only; DO NOT USE) # Index: lib/net/http/generic_request.rb =================================================================== --- lib/net/http/generic_request.rb (revision 35950) +++ lib/net/http/generic_request.rb (working copy) @@ -7,16 +7,29 @@ class Net::HTTPGenericRequest include Net::HTTPHeader - def initialize(m, reqbody, resbody, path, initheader = nil) + def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) @method = m @request_has_body = reqbody @response_has_body = resbody + + if URI === uri_or_path then + @uri = uri_or_path.dup + host = uri.host + host << ":#{uri.port}" if uri.port != uri.class::DEFAULT_PORT + path = uri_or_path.request_uri + else + @uri = nil + host = nil + path = uri_or_path + end + raise ArgumentError, "no HTTP request path given" unless path raise ArgumentError, "HTTP request path is empty" if path.empty? @path = path initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' + self['Host'] ||= host @body = nil @body_stream = nil @body_data = nil @@ -24,6 +37,7 @@ class Net::HTTPGenericRequest attr_reader :method attr_reader :path + attr_reader :uri def inspect "\#<#{self.class} #{@method}>" @@ -81,6 +95,23 @@ class Net::HTTPGenericRequest end end + def update_uri(host, port, ssl) # :nodoc: internal use only + return unless @uri + + @uri.host = host + @uri.port = port + + scheme = ssl ? 'https' : 'http' + + # convert the class of the URI + unless scheme == @uri.scheme then + new_uri = @uri.to_s.sub(/^https?/, scheme) + @uri = URI new_uri + end + + @uri + end + private class Chunker #:nodoc: Index: lib/net/http.rb =================================================================== --- lib/net/http.rb (revision 35950) +++ lib/net/http.rb (working copy) @@ -93,7 +93,7 @@ module Net #:nodoc: # uri = URI('http://example.com/some_path?query=string') # # Net::HTTP.start(uri.host, uri.port) do |http| - # request = Net::HTTP::Get.new uri.request_uri + # request = Net::HTTP::Get.new uri # # response = http.request request # Net::HTTPResponse object # end @@ -111,6 +111,10 @@ module Net #:nodoc: # will automatically open a connection to the server if one is not currently # open. You can manually close the connection with #finish. # + # For all the Net::HTTP request objects and shortcut request methods you may + # supply either a String for the request path or a URI from which Net::HTTP + # will extract the request path. + # # === Response Data # # uri = URI('http://example.com/index.html') @@ -168,7 +172,7 @@ module Net #:nodoc: # creates a urlencoded POST body: # # uri = URI('http://www.example.com/todo.cgi') - # req = Net::HTTP::Post.new(uri.path) + # req = Net::HTTP::Post.new(uri) # req.set_form_data('from' => '2005-01-01', 'to' => '2005-03-31') # # res = Net::HTTP.start(uri.hostname, uri.port) do |http| @@ -186,7 +190,7 @@ module Net #:nodoc: # multipart/form-data use Net::HTTPRequest#body= and # Net::HTTPRequest#content_type=: # - # req = Net::HTTP::Post.new(uri.path) + # req = Net::HTTP::Post.new(uri) # req.body = multipart_data # req.content_type = 'multipart/form-data' # @@ -203,7 +207,7 @@ module Net #:nodoc: # uri = URI('http://example.com/cached_response') # file = File.stat 'cached_response' # - # req = Net::HTTP::Get.new(uri.request_uri) + # req = Net::HTTP::Get.new(uri) # req['If-Modified-Since'] = file.mtime.rfc2822 # # res = Net::HTTP.start(uri.hostname, uri.port) {|http| @@ -221,7 +225,7 @@ module Net #:nodoc: # # uri = URI('http://example.com/index.html?key=value') # - # req = Net::HTTP::Get.new(uri.request_uri) + # req = Net::HTTP::Get.new(uri) # req.basic_auth 'user', 'pass' # # res = Net::HTTP.start(uri.hostname, uri.port) {|http| @@ -238,7 +242,7 @@ module Net #:nodoc: # uri = URI('http://example.com/large_file') # # Net::HTTP.start(uri.host, uri.port) do |http| - # request = Net::HTTP::Get.new uri.request_uri + # request = Net::HTTP::Get.new uri # # http.request request do |response| # open 'large_file', 'w' do |io| @@ -257,7 +261,7 @@ module Net #:nodoc: # # Net::HTTP.start(uri.host, uri.port, # :use_ssl => uri.scheme == 'https') do |http| - # request = Net::HTTP::Get.new uri.request_uri + # request = Net::HTTP::Get.new uri # # response = http.request request # Net::HTTPResponse object # end @@ -453,7 +457,7 @@ module Net #:nodoc: uri = uri_or_host start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') {|http| - return http.request_get(uri.request_uri, &block) + return http.request_get(uri, &block) } end end @@ -477,7 +481,7 @@ module Net #:nodoc: # { "q" => "ruby", "max" => "50" } # def HTTP.post_form(url, params) - req = Post.new(url.request_uri) + req = Post.new(url) req.form_data = params req.basic_auth url.user, url.password if url.user start(url.hostname, url.port, @@ -992,7 +996,9 @@ module Net #:nodoc: public - # Gets data from +path+ on the connected-to host. + # Retrieves data from +path+ on the connected-to host which may be an + # absolute path String or a URI to extract the path from. + # # +initheader+ must be a Hash like { 'Accept' => '*/*', ... }, # and it defaults to an empty hash. # If +initheader+ doesn't have the key 'accept-encoding', then @@ -1336,6 +1342,9 @@ module Net #:nodoc: begin res = HTTPResponse.read_new(@socket) end while res.kind_of?(HTTPContinue) + + res.uri = req.uri + res.reading_body(@socket, req.response_body_permitted?) { yield res if block_given? } @@ -1377,6 +1386,10 @@ module Net #:nodoc: if not req.response_body_permitted? and @close_on_empty_response req['connection'] ||= 'close' end + + host = req['host'] || address + req.update_uri host, port, use_ssl? + req['host'] ||= addr_port() end Index: test/net/http/test_httpresponse.rb =================================================================== --- test/net/http/test_httpresponse.rb (revision 35950) +++ test/net/http/test_httpresponse.rb (working copy) @@ -32,6 +32,21 @@ EOS assert_equal('XXX YYY', res.header['x-bar']) end + def test_uri_equals + uri = URI 'http://example' + + response = Net::HTTPResponse.new '1.1', 200, 'OK' + + response.uri = nil + + assert_nil response.uri + + response.uri = uri + + assert_equal uri, response.uri + refute_same uri, response.uri + end + private def dummy_io(str) Index: test/net/http/test_http.rb =================================================================== --- test/net/http/test_http.rb (revision 35950) +++ test/net/http/test_http.rb (working copy) @@ -224,6 +224,8 @@ module TestNetHTTP_version_1_2_methods _test_request__HEAD http _test_request__POST http _test_request__stream_body http + _test_request__uri http + _test_request__uri_host http } end @@ -303,6 +305,35 @@ module TestNetHTTP_version_1_2_methods assert_equal data, res.body end + def _test_request__uri(http) + uri = URI 'https://example/' + req = Net::HTTP::Get.new(uri) + + res = http.request(req) + + assert_kind_of URI::Generic, req.uri + + refute_equal uri, req.uri + + assert_equal req.uri, res.uri + + refute_same uri, req.uri + refute_same req.uri, res.uri + end + + def _test_request__uri_host(http) + uri = URI 'http://example/' + + req = Net::HTTP::Get.new(uri) + req['host'] = 'other.example' + + res = http.request(req) + + assert_kind_of URI::Generic, req.uri + + assert_equal URI("http://other.example:#{http.port}"), res.uri + end + def test_send_request start {|http| _test_send_request__GET http @@ -616,3 +647,4 @@ class TestNetHTTPKeepAlive < Test::Unit: } end end + Index: test/net/http/test_http_generic_request.rb =================================================================== --- test/net/http/test_http_generic_request.rb (revision 0) +++ test/net/http/test_http_generic_request.rb (revision 0) @@ -0,0 +1,207 @@ +require 'net/http' +require 'test/unit' +require 'stringio' + +class HTTPGenericResponseTest < Test::Unit::TestCase + + def setup + super + + @GR = Net::HTTPGenericRequest + + @request = @GR.new 'POST', true, true, '/' + end + + def test_initialize + request = @GR.new 'GET', false, true, '/index', 'X-Header' => 'value' + + assert_equal 'GET', request.method + assert_equal '/index', request.path + assert_nil request.uri + + assert request.response_body_permitted? + refute request.request_body_permitted? + + assert_equal 'value', request['X-Header'] + assert_nil request['Host'] + end + + def test_initialize_uri + uri = URI 'http://example/index?hello=world' + request = @GR.new 'GET', false, true, uri, 'X-Header' => 'value' + + assert_equal 'GET', request.method + assert_equal '/index?hello=world', request.path + assert_equal uri, request.uri + refute_same uri, request.uri + + assert request.response_body_permitted? + refute request.request_body_permitted? + + assert_equal 'value', request['X-Header'] + + assert_equal 'example', request['Host'] + end + + def test_initialize_uri_host + uri = URI 'http://example/' + request = @GR.new 'GET', false, true, uri, 'Host' => 'other.example' + + assert_equal 'other.example', request['Host'] + end + + def test_initialize_uri_nonstandard_port + uri = URI 'http://example:81' + request = @GR.new 'GET', false, true, uri + + assert_equal 'example:81', request['Host'] + end + + def test_body_equals + @request.body = 'hello' + + assert_equal 'hello', @request.body + end + + def test_body_stream_equals + @request.body_stream = StringIO.new 'hello' + + assert_equal 'hello', @request.body_stream.read + end + + def test_set_body_internal + @request.set_body_internal 'hello' + + assert_equal 'hello', @request.body + end + + def test_set_body_internal_already_set + @request.body = 'hello' + + assert_raises ArgumentError do + @request.set_body_internal 'other' + end + + @request.body = nil + @request.body_stream = StringIO.new 'hello' + + assert_raises ArgumentError do + @request.set_body_internal 'other' + end + end + + def test_send_request_with_body + socket = StringIO.new + def socket.continue_timeout() end + + @request.send :send_request_with_body, socket, '1.1', '/', 'hello' + + expected = <<-EXPECTED.chomp +POST / HTTP/1.1\r +Accept: */*\r +User-Agent: Ruby\r +Content-Length: 5\r +Content-Type: application/x-www-form-urlencoded\r +\r +hello + EXPECTED + + assert_equal expected, socket.string + end + + def test_send_request_with_body_stream + stream = StringIO.new 'hello' + @request['transfer-encoding'] = 'chunked' + + socket = StringIO.new + def socket.continue_timeout() end + + @request.send :send_request_with_body_stream, socket, '1.1', '/', stream + + expected = <<-EXPECTED +POST / HTTP/1.1\r +Accept: */*\r +User-Agent: Ruby\r +Transfer-Encoding: chunked\r +Content-Type: application/x-www-form-urlencoded\r +\r +5\r +hello\r +0\r +\r + EXPECTED + + assert_equal expected, socket.string + end + + def test_send_request_with_body_data_multipart + params = [%w[hello world]] + socket = StringIO.new + def socket.continue_timeout() end + + @request.set_form params, 'multipart/form-data' + + @request.send :send_request_with_body_data, socket, '1.1', '/', params + + sent = socket.string + + sent =~ /^Content-Type: multipart\/form-data; boundary=(.*?)\r\n/ + + boundary = $1 + + sent = sent.gsub(/#{Regexp.escape boundary}/, '') + + expected = <<-EXPECTED +POST / HTTP/1.1\r +Accept: */*\r +User-Agent: Ruby\r +Content-Type: multipart/form-data; boundary=\r +Content-Length: 173\r +\r +--\r +Content-Disposition: form-data; name="hello"\r +\r +world\r +----\r + EXPECTED + + assert_equal expected, sent + end + + def test_send_request_with_body_data_urlencoded + params = %w[hello world] + socket = StringIO.new + def socket.continue_timeout() end + + @request.send :send_request_with_body_data, socket, '1.1', '/', params + + expected = <<-EXPECTED.chomp +POST / HTTP/1.1\r +Accept: */*\r +User-Agent: Ruby\r +Content-Type: application/x-www-form-urlencoded\r +Content-Length: 11\r +\r +hello&world + EXPECTED + + assert_equal expected, socket.string + end + + def test_update_uri + uri = URI 'http://example/index' + request = @GR.new 'GET', false, true, uri, 'X-Header' => 'value' + + request.update_uri 'other.example', 443, true + + assert_equal URI('https://other.example:443/index'), request.uri + end + + def test_update_uri_path + @request.update_uri 'other.example', 80, false + + assert_nil @request.uri + end + +end +