* lib/net/http.rb: Requests may be created with a URI which sets the

Host header.  Responses contain the requested URI for easier redirect
	  following.  [ruby-trunk - Feature #6482]
	* lib/net/http/generic_request.rb:  ditto.
	* lib/net/http/response.rb:  ditto.j
	* NEWS (net/http):  Updated for above.
	* test/net/http/test_http.rb:  Tests for above.
	* test/net/http/test_http.rb:  ditto.
	* test/net/http/test_httpresponse.rb:  ditto.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@38546 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2012-12-21 20:36:07 +00:00
parent 34a3668c30
commit 570b766901
7 changed files with 149 additions and 12 deletions

View File

@ -1,3 +1,15 @@
Sat Dec 22 05:34:54 2012 Eric Hodel <drbrain@segment7.net>
* lib/net/http.rb: Requests may be created with a URI which sets the
Host header. Responses contain the requested URI for easier redirect
following. [ruby-trunk - Feature #6482]
* lib/net/http/generic_request.rb: ditto.
* lib/net/http/response.rb: ditto.
* NEWS (net/http): Updated for above.
* test/net/http/test_http.rb: Tests for above.
* test/net/http/test_http.rb: ditto.
* test/net/http/test_httpresponse.rb: ditto.
Sat Dec 22 02:35:00 2012 Zachary Scott <zachary@zacharyscott.net> Sat Dec 22 02:35:00 2012 Zachary Scott <zachary@zacharyscott.net>
* lib/irb/slex.rb(#match): Typo, should be D_DETAIL * lib/irb/slex.rb(#match): Typo, should be D_DETAIL

4
NEWS
View File

@ -205,6 +205,10 @@ with all sufficient information, see the ChangeLog file.
default. See Net::HTTP for details. default. See Net::HTTP for details.
* SSL sessions are now reused across connections for a single instance. * SSL sessions are now reused across connections for a single instance.
This speeds up connection by using a previously negotiated session. This speeds up connection by using a previously negotiated session.
* Requests may be created from a URI which sets the request_uri and host
header of the request (but does not change the host connected to).
* Responses contain the URI requested which allows easier implementation of
redirect following.
* new methods: * new methods:
* Net::HTTP#local_host * Net::HTTP#local_host
* Net::HTTP#local_host= * Net::HTTP#local_host=

View File

@ -93,7 +93,7 @@ module Net #:nodoc:
# uri = URI('http://example.com/some_path?query=string') # uri = URI('http://example.com/some_path?query=string')
# #
# Net::HTTP.start(uri.host, uri.port) do |http| # 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 # response = http.request request # Net::HTTPResponse object
# end # end
@ -111,6 +111,10 @@ module Net #:nodoc:
# will automatically open a connection to the server if one is not currently # will automatically open a connection to the server if one is not currently
# open. You can manually close the connection with #finish. # 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 # === Response Data
# #
# uri = URI('http://example.com/index.html') # uri = URI('http://example.com/index.html')
@ -168,7 +172,7 @@ module Net #:nodoc:
# creates a urlencoded POST body: # creates a urlencoded POST body:
# #
# uri = URI('http://www.example.com/todo.cgi') # 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') # req.set_form_data('from' => '2005-01-01', 'to' => '2005-03-31')
# #
# res = Net::HTTP.start(uri.hostname, uri.port) do |http| # 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 # multipart/form-data use Net::HTTPRequest#body= and
# Net::HTTPRequest#content_type=: # Net::HTTPRequest#content_type=:
# #
# req = Net::HTTP::Post.new(uri.path) # req = Net::HTTP::Post.new(uri)
# req.body = multipart_data # req.body = multipart_data
# req.content_type = 'multipart/form-data' # req.content_type = 'multipart/form-data'
# #
@ -203,7 +207,7 @@ module Net #:nodoc:
# uri = URI('http://example.com/cached_response') # uri = URI('http://example.com/cached_response')
# file = File.stat '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 # req['If-Modified-Since'] = file.mtime.rfc2822
# #
# res = Net::HTTP.start(uri.hostname, uri.port) {|http| # 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') # 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' # req.basic_auth 'user', 'pass'
# #
# res = Net::HTTP.start(uri.hostname, uri.port) {|http| # res = Net::HTTP.start(uri.hostname, uri.port) {|http|
@ -238,7 +242,7 @@ module Net #:nodoc:
# uri = URI('http://example.com/large_file') # uri = URI('http://example.com/large_file')
# #
# Net::HTTP.start(uri.host, uri.port) do |http| # 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| # http.request request do |response|
# open 'large_file', 'w' do |io| # open 'large_file', 'w' do |io|
@ -257,7 +261,7 @@ module Net #:nodoc:
# #
# Net::HTTP.start(uri.host, uri.port, # Net::HTTP.start(uri.host, uri.port,
# :use_ssl => uri.scheme == 'https') do |http| # :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 # response = http.request request # Net::HTTPResponse object
# end # end
@ -472,7 +476,7 @@ module Net #:nodoc:
uri = uri_or_host uri = uri_or_host
start(uri.hostname, uri.port, start(uri.hostname, uri.port,
:use_ssl => uri.scheme == 'https') {|http| :use_ssl => uri.scheme == 'https') {|http|
return http.request_get(uri.request_uri, &block) return http.request_get(uri, &block)
} }
end end
end end
@ -496,7 +500,7 @@ module Net #:nodoc:
# { "q" => "ruby", "max" => "50" } # { "q" => "ruby", "max" => "50" }
# #
def HTTP.post_form(url, params) def HTTP.post_form(url, params)
req = Post.new(url.request_uri) req = Post.new(url)
req.form_data = params req.form_data = params
req.basic_auth url.user, url.password if url.user req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port, start(url.hostname, url.port,
@ -868,7 +872,7 @@ module Net #:nodoc:
conn_port = port conn_port = port
end end
D "opening connection to #{conn_address}..." D "opening connection to #{conn_address}:#{conn_port}..."
s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
TCPSocket.open(conn_address, conn_port, @local_host, @local_port) TCPSocket.open(conn_address, conn_port, @local_host, @local_port)
} }
@ -884,8 +888,10 @@ module Net #:nodoc:
end end
@ssl_context = OpenSSL::SSL::SSLContext.new @ssl_context = OpenSSL::SSL::SSLContext.new
@ssl_context.set_params(ssl_parameters) @ssl_context.set_params(ssl_parameters)
D "starting SSL for #{conn_address}:#{conn_port}..."
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
s.sync_close = true s.sync_close = true
D "SSL established"
end end
@socket = BufferedIO.new(s) @socket = BufferedIO.new(s)
@socket.read_timeout = @read_timeout @socket.read_timeout = @read_timeout
@ -1077,7 +1083,9 @@ module Net #:nodoc:
public 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' => '*/*', ... }, # +initheader+ must be a Hash like { 'Accept' => '*/*', ... },
# and it defaults to an empty hash. # and it defaults to an empty hash.
# If +initheader+ doesn't have the key 'accept-encoding', then # If +initheader+ doesn't have the key 'accept-encoding', then
@ -1403,6 +1411,9 @@ module Net #:nodoc:
begin begin
res = HTTPResponse.read_new(@socket) res = HTTPResponse.read_new(@socket)
end while res.kind_of?(HTTPContinue) end while res.kind_of?(HTTPContinue)
res.uri = req.uri
res.reading_body(@socket, req.response_body_permitted?) { res.reading_body(@socket, req.response_body_permitted?) {
yield res if block_given? yield res if block_given?
} }
@ -1444,6 +1455,11 @@ module Net #:nodoc:
if not req.response_body_permitted? and @close_on_empty_response if not req.response_body_permitted? and @close_on_empty_response
req['connection'] ||= 'close' req['connection'] ||= 'close'
end end
host = req['host'] || address
host = $1 if host =~ /(.*):\d+$/
req.update_uri host, port, use_ssl?
req['host'] ||= addr_port() req['host'] ||= addr_port()
end end

View File

@ -7,10 +7,22 @@ class Net::HTTPGenericRequest
include Net::HTTPHeader include Net::HTTPHeader
def initialize(m, reqbody, resbody, path, initheader = nil) def initialize(m, reqbody, resbody, uri_or_path, initheader = nil)
@method = m @method = m
@request_has_body = reqbody @request_has_body = reqbody
@response_has_body = resbody @response_has_body = resbody
if URI === uri_or_path then
@uri = uri_or_path.dup
host = @uri.hostname
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, "no HTTP request path given" unless path
raise ArgumentError, "HTTP request path is empty" if path.empty? raise ArgumentError, "HTTP request path is empty" if path.empty?
@path = path @path = path
@ -29,6 +41,7 @@ class Net::HTTPGenericRequest
initialize_http_header initheader initialize_http_header initheader
self['Accept'] ||= '*/*' self['Accept'] ||= '*/*'
self['User-Agent'] ||= 'Ruby' self['User-Agent'] ||= 'Ruby'
self['Host'] ||= host
@body = nil @body = nil
@body_stream = nil @body_stream = nil
@body_data = nil @body_data = nil
@ -36,6 +49,7 @@ class Net::HTTPGenericRequest
attr_reader :method attr_reader :method
attr_reader :path attr_reader :path
attr_reader :uri
def inspect def inspect
"\#<#{self.class} #{@method}>" "\#<#{self.class} #{@method}>"
@ -82,6 +96,8 @@ class Net::HTTPGenericRequest
# #
def exec(sock, ver, path) #:nodoc: internal use only def exec(sock, ver, path) #:nodoc: internal use only
self['host'] = "#{@uri.host}:#{@uri.port}" if @uri
if @body if @body
send_request_with_body sock, ver, path, @body send_request_with_body sock, ver, path, @body
elsif @body_stream elsif @body_stream
@ -93,6 +109,23 @@ class Net::HTTPGenericRequest
end end
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 private
class Chunker #:nodoc: class Chunker #:nodoc:

View File

@ -79,6 +79,7 @@ class Net::HTTPResponse
initialize_http_header nil initialize_http_header nil
@body = nil @body = nil
@read = false @read = false
@uri = nil
end end
# The HTTP version supported by the server. # The HTTP version supported by the server.
@ -93,6 +94,10 @@ class Net::HTTPResponse
attr_reader :message attr_reader :message
alias msg message # :nodoc: obsolete 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 def inspect
"#<#{self.class} #{@code} #{@message} readbody=#{@read}>" "#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
end end
@ -118,6 +123,10 @@ class Net::HTTPResponse
error! unless self.kind_of?(Net::HTTPSuccess) error! unless self.kind_of?(Net::HTTPSuccess)
end end
def uri= uri # :nodoc:
@uri = uri.dup if uri
end
# #
# header (for backward compatibility only; DO NOT USE) # header (for backward compatibility only; DO NOT USE)
# #

View File

@ -409,6 +409,8 @@ module TestNetHTTP_version_1_2_methods
_test_request__HEAD http _test_request__HEAD http
_test_request__POST http _test_request__POST http
_test_request__stream_body http _test_request__stream_body http
_test_request__uri http
_test_request__uri_host http
} }
end end
@ -488,6 +490,51 @@ module TestNetHTTP_version_1_2_methods
assert_equal data, res.body assert_equal data, res.body
end end
def _test_request__path(http)
uri = URI 'https://example/'
req = Net::HTTP::Get.new('/')
res = http.request(req)
assert_kind_of URI::Generic, req.uri
refute_equal uri, req.uri
assert_equal uri, res.uri
refute_same uri, req.uri
refute_same req.uri, res.uri
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://example:#{http.port}"), res.uri
end
def test_send_request def test_send_request
start {|http| start {|http|
_test_send_request__GET http _test_send_request__GET http
@ -837,3 +884,4 @@ class TestNetHTTPLocalBind < Test::Unit::TestCase
assert_equal(http.local_port, res.body) assert_equal(http.local_port, res.body)
end end
end end

View File

@ -203,6 +203,21 @@ EOS
assert_equal 'hello', body assert_equal 'hello', body
end 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 private
def dummy_io(str) def dummy_io(str)