summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-03-06 07:42:35 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-03-06 07:42:35 +0000
commit5e4004fa1d3b4a045c8492fd37812c6103dcc68a (patch)
tree303047031beae9df66b5d1055d93917ee550bad4
parent81e29c1cd9258629940b23531007d37d3333f126 (diff)
* lib/net/http.rb: HTTPHeader holds its header fields as an array (backport from CVS HEAD rev 1.112-1.123). [ruby-list:40629]
* test/net/http/test_httpheader.rb: new file. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@8089 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog7
-rw-r--r--lib/net/http.rb183
-rw-r--r--test/net/http/test_httpheader.rb82
3 files changed, 208 insertions, 64 deletions
diff --git a/ChangeLog b/ChangeLog
index a156b6810c..c13a183951 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Sun Mar 6 16:41:33 2005 Minero Aoki <aamine@loveruby.net>
+
+ * lib/net/http.rb: HTTPHeader holds its header fields as an array
+ (backport from CVS HEAD rev 1.112-1.123). [ruby-list:40629]
+
+ * test/net/http/test_httpheader.rb: new file.
+
Sun Mar 6 11:47:10 2005 Sam Roberts <sroberts@uniserve.com>
* lib/pp.rb: rdoced. [ruby-core:4490]
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 8ae2889843..a0b46e117d 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -1,8 +1,8 @@
#
# = net/http.rb
#
-# Copyright (c) 1999-2003 Yukihiro Matsumoto
-# Copyright (c) 1999-2003 Minero Aoki
+# Copyright (c) 1999-2005 Yukihiro Matsumoto
+# Copyright (c) 1999-2005 Minero Aoki
#
# Written & maintained by Minero Aoki <aamine@loveruby.net>.
#
@@ -26,7 +26,6 @@
require 'net/protocol'
require 'uri'
-
module Net # :nodoc:
# :stopdoc:
@@ -48,9 +47,9 @@ module Net # :nodoc:
# (formal version)
#
# require 'net/http'
- # Net::HTTP.start('www.example.com', 80) { |http|
- # response = http.get('/index.html')
- # puts response.body
+ # Net::HTTP.start('www.example.com', 80) {|http|
+ # response = http.get('/index.html')
+ # puts response.body
# }
#
# (shorter version)
@@ -67,8 +66,8 @@ module Net # :nodoc:
# === Posting Form Data
#
# require 'net/http'
- # Net::HTTP.start('some.www.server', 80) { |http|
- # response = http.post('/cgi-bin/search.rb', 'query=ruby')
+ # Net::HTTP.start('some.www.server', 80) {|http|
+ # response = http.post('/cgi-bin/search.rb', 'query=ruby')
# }
#
# === Accessing via Proxy
@@ -83,7 +82,7 @@ module Net # :nodoc:
# proxy_port = 8080
# :
# Net::HTTP::Proxy(proxy_addr, proxy_port).start('www.example.com') {|http|
- # # always connect to your.proxy.addr:8080
+ # # always connect to your.proxy.addr:8080
# :
# }
#
@@ -118,7 +117,7 @@ module Net # :nodoc:
# require 'net/http'
# require 'uri'
#
- # def fetch( uri_str, limit = 10 )
+ # def fetch(uri_str, limit = 10)
# # You should choose better exception.
# raise ArgumentError, 'HTTP redirect too deep' if limit == 0
#
@@ -160,13 +159,13 @@ module Net # :nodoc:
# allows you to use 1.2 features again.
#
# # example
- # Net::HTTP.start { |http1| ...(http1 has 1.2 features)... }
+ # Net::HTTP.start {|http1| ...(http1 has 1.2 features)... }
#
# Net::HTTP.version_1_1
- # Net::HTTP.start { |http2| ...(http2 has 1.1 features)... }
+ # Net::HTTP.start {|http2| ...(http2 has 1.1 features)... }
#
# Net::HTTP.version_1_2
- # Net::HTTP.start { |http3| ...(http3 has 1.2 features)... }
+ # Net::HTTP.start {|http3| ...(http3 has 1.2 features)... }
#
# This function is NOT thread-safe.
#
@@ -831,7 +830,7 @@ module Net # :nodoc:
req.exec @socket, @curr_http_version, edit_path(req.path), body
begin
res = HTTPResponse.read_new(@socket)
- end while HTTPContinue === res
+ end while res.kind_of?(HTTPContinue)
res.reading_body(@socket, req.response_body_permitted?) {
yield res if block_given?
}
@@ -913,6 +912,17 @@ module Net # :nodoc:
#
module HTTPHeader
+ def initialize_http_header(h)
+ @header = {}
+ return unless h
+ h.each do |k,v|
+ key = k.downcase
+ $stderr.puts "net/http: warning: duplicated HTTP header: #{k}" if @header.key?(key) and $VERBOSE
+ @header[key] = [v.strip]
+ end
+ end
+ private :initialize_http_header
+
def size #:nodoc: obsolete
@header.size
end
@@ -920,26 +930,83 @@ module Net # :nodoc:
alias length size #:nodoc: obsolete
# Returns the header field corresponding to the case-insensitive key.
- # For example, a key of "Content-Type" might return "text/html"
+ # See also #get_fields.
+ #
+ # p response['Content-Type'] #=> "text/html; charset=utf-8"
+ # p response['cOnTeNt-tYpE'] #=> "text/html; charset=utf-8"
+ #
def [](key)
- @header[key.downcase]
+ a = @header[key.downcase] or return nil
+ a.join(', ')
+ end
+
+ # [Ruby 1.8.3]
+ # Returns an array of header field strings corresponding to the
+ # case-insensitive +key+. This method allows you to get duplicated
+ # header fields without any processing. See also #[].
+ #
+ # p response.get_fields('Set-Cookie')
+ # #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23",
+ # "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"]
+ # p response['Set-Cookie']
+ # #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"
+ #
+ def get_fields(key)
+ return nil unless @header[key.downcase]
+ @header[key.downcase].dup
end
# Sets the header field corresponding to the case-insensitive key.
+ # See also #add_field.
+ #
+ # request['My-Header'] = 'a'
+ # p request['My-Header'] #=> "a"
+ # request['My-Header'] = 'b'
+ # p request['My-Header'] #=> "b"
+ #
def []=(key, val)
- @header[key.downcase] = val
+ unless val
+ @header.delete key.downcase
+ return val
+ end
+ @header[key.downcase] = [val].flatten.map {|s| s.to_str }
+ end
+
+ # [Ruby 1.8.3]
+ # Adds header field instead of replace.
+ # Second argument +val+ must be a String.
+ # See also #[]=, #[] and #get_fields.
+ #
+ # request.add_field 'X-My-Header', 'a'
+ # p request['X-My-Header'] #=> "a"
+ # p request.get_fields('X-My-Header') #=> ["a"]
+ # request.add_field 'X-My-Header', 'b'
+ # p request['X-My-Header'] #=> "a, b"
+ # p request.get_fields('X-My-Header') #=> ["a", "b"]
+ # request.add_field 'X-My-Header', 'c'
+ # p request['X-My-Header'] #=> "a, b, c"
+ # p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
+ #
+ def add_field(key, val)
+ if @header[key.downcase]
+ @header[key.downcase].push val
+ else
+ @header[key.downcase] = [val]
+ end
end
# Returns the header field corresponding to the case-insensitive key.
# Returns the default value +args+, or the result of the block, or nil,
# if there's no header field named key. See Hash#fetch
def fetch(key, *args, &block) #:yield: +key+
- @header.fetch(key.downcase, *args, &block)
+ @header.fetch(key.downcase, *args, &block).join(', ')
end
# Iterates for each header names and values.
def each_header(&block) #:yield: +key+, +value+
- @header.each(&block)
+ @header.each do |k, va|
+ yield k, va.join(', ')
+ end
end
alias each each_header
@@ -951,7 +1018,9 @@ module Net # :nodoc:
# Iterates for each header values.
def each_value(&block) #:yield: +value+
- @header.each_value(&block)
+ @header.each_value do |va|
+ yield va.join(', ')
+ end
end
# Removes a header field.
@@ -966,17 +1035,21 @@ module Net # :nodoc:
# Returns a Hash consist of header names and values.
def to_hash
- @header.dup
+ h = {}
+ @header.each do |k, va|
+ h[k] = va.join(', ')
+ end
+ h
end
# As for #each_header, except the keys are provided in capitalized form.
def canonical_each
- @header.each do |k,v|
- yield canonical(k), v
+ @header.each do |k, va|
+ yield canonical(k), va.join(', ')
end
end
- def canonical( k )
+ def canonical(k)
k.split(/-/).map {|i| i.capitalize }.join('-')
end
private :canonical
@@ -984,8 +1057,8 @@ module Net # :nodoc:
# Returns a Range object which represents Range: header field,
# or +nil+ if there is no such header.
def range
- s = @header['range'] or return nil
- s.split(/,/).map {|spec|
+ return nil unless @header['range']
+ self['Range'].split(/,/).map {|spec|
m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match(spec) or
raise HTTPHeaderSyntaxError, "wrong Range: #{spec}"
d1 = m[1].to_i
@@ -1025,7 +1098,7 @@ module Net # :nodoc:
raise TypeError, 'Range/Integer is required'
end
- @header['range'] = "bytes=#{s}"
+ @header['range'] = ["bytes=#{s}"]
r
end
@@ -1034,10 +1107,10 @@ module Net # :nodoc:
# Returns an Integer object which represents the Content-Length: header field
# or +nil+ if that field is not provided.
def content_length
- s = @header['content-length'] or return nil
- m = /\d+/.match(s) or
+ return nil unless @header['content-length']
+ len = self['Content-Length'].slice(/\d+/) or
raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
- m[0].to_i
+ len.to_i
end
# Returns "true" if the "transfer-encoding" header is present and
@@ -1045,34 +1118,35 @@ module Net # :nodoc:
# the content to be sent in "chunks" without at the outset
# stating the entire content length.
def chunked?
- s = @header['transfer-encoding']
- (s and /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i === s) ? true : false
+ return false unless @header.key?('transfer-encoding')
+ s = self['Transfer-Encoding']
+ /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i =~ s ? true : false
end
# Returns a Range object which represents Content-Range: header field.
# This indicates, for a partial entity body, where this fragment
# fits inside the full entity body, as range of byte offsets.
def content_range
- s = @header['content-range'] or return nil
- m = %r<bytes\s+(\d+)-(\d+)/(?:\d+|\*)>i.match(s) or
+ return nil unless @header['content-range']
+ m = %r<bytes\s+(\d+)-(\d+)/(?:\d+|\*)>i.match(self['Content-Range']) or
raise HTTPHeaderSyntaxError, 'wrong Content-Range format'
m[1].to_i .. m[2].to_i + 1
end
# The length of the range represented in Range: header.
def range_length
- r = self.content_range
- r and (r.end - r.begin)
+ r = content_range() or return nil
+ r.end - r.begin
end
# Set the Authorization: header for "Basic" authorization.
def basic_auth(account, password)
- @header['authorization'] = basic_encode(account, password)
+ @header['authorization'] = [basic_encode(account, password)]
end
# Set Proxy-Authorization: header for "Basic" authorization.
def proxy_basic_auth(account, password)
- @header['proxy-authorization'] = basic_encode(account, password)
+ @header['proxy-authorization'] = [basic_encode(account, password)]
end
def basic_encode(account, password)
@@ -1097,15 +1171,8 @@ module Net # :nodoc:
@request_has_body = reqbody
@response_has_body = resbody
@path = path
-
- @header = {}
- return unless initheader
- initheader.each do |k,v|
- key = k.downcase
- $stderr.puts "net/http: warning: duplicated HTTP header: #{k}" if @header.key?(key) and $VERBOSE
- @header[key] = v.strip
- end
- @header['accept'] ||= '*/*'
+ initialize_http_header initheader
+ self['Accept'] ||= '*/*'
end
attr_reader :method
@@ -1145,15 +1212,13 @@ module Net # :nodoc:
raise ArgumentError, 'HTTP request body is not permitted'
end
- def send_request_with_body( sock, ver, path, body )
- @header['content-length'] = body.length.to_s
- @header.delete 'transfer-encoding'
-
- unless @header['content-type']
+ def send_request_with_body(sock, ver, path, body)
+ self['Content-Length'] = body.length.to_s
+ self.delete 'Transfer-Encoding'
+ unless self['Content-Type']
$stderr.puts 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
- @header['content-type'] = 'application/x-www-form-urlencoded'
+ self['Content-Type'] = 'application/x-www-form-urlencoded'
end
-
request sock, ver, path
sock.write body
end
@@ -1538,13 +1603,8 @@ module Net # :nodoc:
httpv, code, msg = read_status_line(sock)
res = response_class(code).new(httpv, code, msg)
each_response_header(sock) do |k,v|
- if res.key?(k)
- res[k] << ', ' << v
- else
- res[k] = v
- end
+ res.add_field k, v
end
-
res
end
@@ -1585,8 +1645,7 @@ module Net # :nodoc:
@http_version = httpv
@code = code
@message = msg
-
- @header = {}
+ initialize_http_header nil
@body = nil
@read = false
end
diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb
index 1a8751e28c..2c64e31f10 100644
--- a/test/net/http/test_httpheader.rb
+++ b/test/net/http/test_httpheader.rb
@@ -6,7 +6,7 @@ class HTTPHeaderTest < Test::Unit::TestCase
class C
include Net::HTTPHeader
def initialize
- @header = {}
+ initialize_http_header({})
end
end
@@ -49,25 +49,91 @@ class HTTPHeaderTest < Test::Unit::TestCase
@c['Next-Header'] = 'next string'
assert_equal 'next string', @c['next-header']
end
-
+
def test_add_field
+ @c.add_field 'My-Header', 'a'
+ assert_equal 'a', @c['My-Header']
+ assert_equal ['a'], @c.get_fields('My-Header')
+ @c.add_field 'My-Header', 'b'
+ assert_equal 'a, b', @c['My-Header']
+ assert_equal ['a', 'b'], @c.get_fields('My-Header')
+ @c.add_field 'My-Header', 'c'
+ assert_equal 'a, b, c', @c['My-Header']
+ assert_equal ['a', 'b', 'c'], @c.get_fields('My-Header')
+ @c.add_field 'My-Header', 'd, d'
+ assert_equal 'a, b, c, d, d', @c['My-Header']
+ assert_equal ['a', 'b', 'c', 'd, d'], @c.get_fields('My-Header')
end
def test_get_fields
+ @c['My-Header'] = 'test string'
+ assert_equal ['test string'], @c.get_fields('my-header')
+ assert_equal ['test string'], @c.get_fields('My-header')
+ assert_equal ['test string'], @c.get_fields('my-Header')
+
+ assert_nil @c.get_fields('not-found')
+ assert_nil @c.get_fields('Not-Found')
+
+ @c.get_fields('my-header').push 'junk'
+ assert_equal ['test string'], @c.get_fields('my-header')
+ @c.get_fields('my-header').clear
+ assert_equal ['test string'], @c.get_fields('my-header')
end
def test_delete
+ @c['My-Header'] = 'test'
+ assert_equal 'test', @c['My-Header']
+ assert_nil @c['not-found']
+ @c.delete 'My-Header'
+ assert_nil @c['My-Header']
+ assert_nil @c['not-found']
+ @c.delete 'My-Header'
+ @c.delete 'My-Header'
+ assert_nil @c['My-Header']
+ assert_nil @c['not-found']
end
def test_each
+ @c['My-Header'] = 'test'
+ @c.each do |k, v|
+ assert_equal 'my-header', k
+ assert_equal 'test', v
+ end
+ @c.each do |k, v|
+ assert_equal 'my-header', k
+ assert_equal 'test', v
+ end
end
def test_each_key
+ @c['My-Header'] = 'test'
+ @c.each_key do |k|
+ assert_equal 'my-header', k
+ end
+ @c.each_key do |k|
+ assert_equal 'my-header', k
+ end
end
def test_each_value
+ @c['My-Header'] = 'test'
+ @c.each_value do |v|
+ assert_equal 'test', v
+ end
+ @c.each_value do |v|
+ assert_equal 'test', v
+ end
+ end
+
+ def test_canonical_each
+ @c['my-header'] = ['a', 'b']
+ @c.canonical_each do |k,v|
+ assert_equal 'My-Header', k
+ assert_equal 'a, b', v
+ end
end
+=begin
def test_each_capitalized
@c['my-header'] = ['a', 'b']
@c.each_capitalized do |k,v|
@@ -75,8 +141,16 @@ class HTTPHeaderTest < Test::Unit::TestCase
assert_equal 'a, b', v
end
end
+=end
def test_key?
+ @c['My-Header'] = 'test'
+ assert_equal true, @c.key?('My-Header')
+ assert_equal true, @c.key?('my-header')
+ assert_equal false, @c.key?('Not-Found')
+ assert_equal false, @c.key?('not-found')
+ assert_equal false, @c.key?('')
+ assert_equal false, @c.key?('x' * 1024)
end
def test_to_hash
@@ -153,6 +227,7 @@ class HTTPHeaderTest < Test::Unit::TestCase
assert_equal len, @c.content_length
end
+=begin
def test_content_length=
@c.content_length = 0
assert_equal 0, @c.content_length
@@ -163,7 +238,9 @@ class HTTPHeaderTest < Test::Unit::TestCase
@c.content_length = 10000000000000
assert_equal 10000000000000, @c.content_length
end
+=end
+=begin
def test_content_type
@c.content_type = 'text/html'
assert_equal 'text/html', @c.content_type
@@ -202,6 +279,7 @@ class HTTPHeaderTest < Test::Unit::TestCase
def test_set_content_type
end
+=end
def test_basic_auth
end