mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/net/http.rb: HTTPHeader keeps its header fields as an array.
* lib/net/http.rb: new method HTTPHeader#add_header, get_fields. * lib/net/http.rb: new method HTTPHeader#content_length=. * lib/net/http.rb: new method HTTPHeader#content_type, main_type, sub_type, type_params, content_type=, set_content_type. * lib/net/http.rb (HTTPHeader#basic_encode): result of pack(m) may contain multiple LFs. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5910 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
316bf4e67b
commit
39848fe845
3 changed files with 227 additions and 53 deletions
14
ChangeLog
14
ChangeLog
|
@ -1,3 +1,17 @@
|
|||
Sun Mar 7 05:34:42 2004 Minero Aoki <aamine@loveruby.net>
|
||||
|
||||
* lib/net/http.rb: HTTPHeader keeps its header fields as an array.
|
||||
|
||||
* lib/net/http.rb: new method HTTPHeader#add_header, get_fields.
|
||||
|
||||
* lib/net/http.rb: new method HTTPHeader#content_length=.
|
||||
|
||||
* lib/net/http.rb: new method HTTPHeader#content_type, main_type,
|
||||
sub_type, type_params, content_type=, set_content_type.
|
||||
|
||||
* lib/net/http.rb (HTTPHeader#basic_encode): result of pack(m) may
|
||||
contain multiple LFs.
|
||||
|
||||
Sun Mar 7 03:11:00 2004 Minero Aoki <aamine@loveruby.net>
|
||||
|
||||
* lib/net/http.rb: new method Net::HTTPRequest#body(=).
|
||||
|
|
179
lib/net/http.rb
179
lib/net/http.rb
|
@ -1021,36 +1021,88 @@ module Net # :nodoc:
|
|||
# Returns the header field corresponding to the case-insensitive key.
|
||||
# For example, a key of "Content-Type" might return "text/html"
|
||||
def [](key)
|
||||
@header[key.downcase]
|
||||
a = @header[key.downcase] or return nil
|
||||
a.join(', ')
|
||||
end
|
||||
|
||||
# Sets the header field corresponding to the case-insensitive key.
|
||||
def []=(key, val)
|
||||
@header[key.downcase] = val
|
||||
unless val
|
||||
@header.delete key.downcase
|
||||
return val
|
||||
end
|
||||
@header[key.downcase] = Array(val).map {|s| s.to_str }
|
||||
end
|
||||
|
||||
# Adds header name and field instead of replace.
|
||||
#
|
||||
# request.add_header 'X-My-Header', 'a'
|
||||
# p request['X-My-Header'] #=> "a"
|
||||
# request.add_header 'X-My-Header', 'b'
|
||||
# p request['X-My-Header'] #=> "a, b"
|
||||
# request.add_header 'X-My-Header', 'c'
|
||||
# p request['X-My-Header'] #=> "a, b, c"
|
||||
# p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
|
||||
#
|
||||
def add_header(key, val)
|
||||
if header.key?(key.downcase)
|
||||
@header[key.downcase].concat Array(val)
|
||||
else
|
||||
@header[key.downcase] = Array(val).dup
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the header field by Array, corresponding to the
|
||||
# case-insensitive key. This method allows you to get duplicated
|
||||
# fields without any processing.
|
||||
#
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
a = @header.fetch(key.downcase, *args, &block)
|
||||
a.join(', ')
|
||||
end
|
||||
|
||||
# Iterates for each header names and values.
|
||||
def each_header(&block) #:yield: +key+, +value+
|
||||
@header.each(&block)
|
||||
def each_header #:yield: +key+, +value+
|
||||
@header.each do |k,va|
|
||||
yield k, va.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
alias each each_header
|
||||
|
||||
# Iterates for each header names.
|
||||
def each_key(&block) #:yield: +key+
|
||||
@header.each_key(&block)
|
||||
def each_name(&block) #:yield: +key+
|
||||
e @header.each_key(&block)
|
||||
end
|
||||
|
||||
alias each_key each_name
|
||||
|
||||
# Iterates for each capitalized header names.
|
||||
def each_capitalized_name(&block) #:yield: +key+
|
||||
@header.each_key do |k|
|
||||
yield capitalize(k)
|
||||
end
|
||||
end
|
||||
|
||||
# Iterates for each header values.
|
||||
def each_value(&block) #:yield: +value+
|
||||
@header.each_value(&block)
|
||||
def each_value #:yield: +value+
|
||||
@header.each_value do |va|
|
||||
yield va.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
# Removes a header field.
|
||||
|
@ -1077,16 +1129,16 @@ module Net # :nodoc:
|
|||
|
||||
alias canonical_each each_capitalized
|
||||
|
||||
def capitalize(k)
|
||||
k.split(/-/).map {|i| i.capitalize }.join('-')
|
||||
def capitalize(name)
|
||||
name.split(/-/).map {|s| s.capitalize }.join('-')
|
||||
end
|
||||
private :capitalize
|
||||
|
||||
# Returns a Range object which represents Range: header field,
|
||||
# Returns an Array of Range objects 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
|
||||
|
@ -1101,12 +1153,21 @@ module Net # :nodoc:
|
|||
end
|
||||
|
||||
# Set Range: header from Range (arg r) or beginning index and
|
||||
# length from it (arg i&len).
|
||||
def range=(r, fin = nil)
|
||||
r = (r ... r + fin) if fin
|
||||
# length from it (arg idx&len).
|
||||
#
|
||||
# req.range = (0..1023)
|
||||
# req.set_range 0, 1023
|
||||
#
|
||||
def set_range(r, e = nil)
|
||||
unless r
|
||||
@header.delete 'range'
|
||||
return r
|
||||
end
|
||||
r = (r...r+e) if e
|
||||
case r
|
||||
when Numeric
|
||||
rangestr = (r > 0 ? "0-#{r.to_i - 1}" : "-#{-r.to_i}")
|
||||
n = r.to_i
|
||||
rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
|
||||
when Range
|
||||
first = r.first
|
||||
last = r.last
|
||||
|
@ -1122,28 +1183,37 @@ module Net # :nodoc:
|
|||
else
|
||||
raise TypeError, 'Range/Integer is required'
|
||||
end
|
||||
@header['range'] = "bytes=#{rangestr}"
|
||||
@header['range'] = ["bytes=#{rangestr}"]
|
||||
r
|
||||
end
|
||||
|
||||
alias set_range range=
|
||||
alias range= set_range
|
||||
|
||||
# Returns an Integer object which represents the Content-Length: header field
|
||||
# or +nil+ if that field is not provided.
|
||||
def content_length
|
||||
return nil unless @header.key?('content-length')
|
||||
len = @header['content-length'].slice(/\d+/) or
|
||||
return nil unless key?('Content-Length')
|
||||
len = self['Content-Length'].slice(/\d+/) or
|
||||
raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
|
||||
len.to_i
|
||||
end
|
||||
|
||||
def content_length=(len)
|
||||
unless len
|
||||
@header.delete 'content-length'
|
||||
return nil
|
||||
end
|
||||
@header['content-length'] = [len.to_i.to_s]
|
||||
end
|
||||
|
||||
# Returns "true" if the "transfer-encoding" header is present and
|
||||
# set to "chunked". This is an HTTP/1.1 feature, allowing the
|
||||
# the content to be sent in "chunks" without at the outset
|
||||
# stating the entire content length.
|
||||
def chunked?
|
||||
field = @header['transfer-encoding'] or return false
|
||||
(/(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i =~ field) ? true : false
|
||||
return false unless @header['transfer-encoding']
|
||||
field = self['Transfer-Encoding']
|
||||
(/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
|
||||
end
|
||||
|
||||
# Returns a Range object which represents Content-Range: header field.
|
||||
|
@ -1151,29 +1221,58 @@ module Net # :nodoc:
|
|||
# fits inside the full entity body, as range of byte offsets.
|
||||
def content_range
|
||||
return nil unless @header['content-range']
|
||||
m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(@header['content-range']) or
|
||||
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.
|
||||
# The length of the range represented in Content-Range: header.
|
||||
def range_length
|
||||
r = content_range() or return nil
|
||||
r.end - r.begin
|
||||
end
|
||||
|
||||
def content_type
|
||||
"#{main_type()}/#{sub_type()}"
|
||||
end
|
||||
|
||||
def main_type
|
||||
return nil unless @header['content-type']
|
||||
self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
|
||||
end
|
||||
|
||||
def sub_type
|
||||
return nil unless @header['content-type']
|
||||
self['Content-Type'].split(';').first.to_s.split('/')[1].to_s.strip
|
||||
end
|
||||
|
||||
def type_params
|
||||
result = {}
|
||||
self['Content-Type'].to_s.split(';')[1..-1].each do |param|
|
||||
k, v = *param.split('=', 2)
|
||||
result[k.strip] = v.strip
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def set_content_type(type, params = {})
|
||||
@header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
|
||||
end
|
||||
|
||||
alias content_type= set_content_type
|
||||
|
||||
# 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)
|
||||
'Basic ' + ["#{account}:#{password}"].pack('m').strip
|
||||
'Basic ' + ["#{account}:#{password}"].pack('m').delete("\r\n")
|
||||
end
|
||||
private :basic_encode
|
||||
|
||||
|
@ -1269,23 +1368,21 @@ module Net # :nodoc:
|
|||
private
|
||||
|
||||
def send_request_with_body(sock, ver, path, body)
|
||||
@header['content-length'] = body.length.to_s
|
||||
@header.delete 'transfer-encoding'
|
||||
unless @header['content-type']
|
||||
self.content_length = body.length
|
||||
delete 'Transfer-Encoding'
|
||||
unless content_type()
|
||||
warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
|
||||
@header['content-type'] = 'application/x-www-form-urlencoded'
|
||||
set_content_type 'application/x-www-form-urlencoded'
|
||||
end
|
||||
write_header sock, ver, path
|
||||
sock.write body
|
||||
end
|
||||
|
||||
def send_request_with_body_stream(sock, ver, path, f)
|
||||
unless @header['content-length']
|
||||
raise ArgumentError, "request body Content-Length not given but Transfer-Encoding is not `chunked'; give up" unless chunked?
|
||||
end
|
||||
unless @header['content-type']
|
||||
raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
|
||||
unless content_type()
|
||||
warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
|
||||
@header['content-type'] = 'application/x-www-form-urlencoded'
|
||||
set_content_type 'application/x-www-form-urlencoded'
|
||||
end
|
||||
write_header sock, ver, path
|
||||
if chunked?
|
||||
|
@ -1730,11 +1827,7 @@ 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_header k, v
|
||||
end
|
||||
res
|
||||
end
|
||||
|
|
|
@ -49,6 +49,30 @@ class HTTPHeaderTest < Test::Unit::TestCase
|
|||
@c['Next-Header'] = 'next string'
|
||||
assert_equal 'next string', @c['next-header']
|
||||
end
|
||||
|
||||
def test_add_field
|
||||
end
|
||||
|
||||
def test_get_fields
|
||||
end
|
||||
|
||||
def test_delete
|
||||
end
|
||||
|
||||
def test_each
|
||||
end
|
||||
|
||||
def test_each_key
|
||||
end
|
||||
|
||||
def test_each_value
|
||||
end
|
||||
|
||||
def test_key?
|
||||
end
|
||||
|
||||
def test_to_hash
|
||||
end
|
||||
|
||||
def test_range
|
||||
try_range(1..5, '1-5')
|
||||
|
@ -59,8 +83,7 @@ class HTTPHeaderTest < Test::Unit::TestCase
|
|||
|
||||
def try_range(r, s)
|
||||
@c['range'] = "bytes=#{s}"
|
||||
ret, = @c.range
|
||||
assert_equal r, ret
|
||||
assert_equal r, Array(@c.range)[0]
|
||||
end
|
||||
|
||||
def test_range=
|
||||
|
@ -76,6 +99,18 @@ class HTTPHeaderTest < Test::Unit::TestCase
|
|||
assert_equal 'bytes=0-499', @c['range']
|
||||
end
|
||||
|
||||
def test_content_range
|
||||
end
|
||||
|
||||
def test_range_length
|
||||
@c['Content-Range'] = "bytes 0-499/1000"
|
||||
assert_equal 500, @c.range_length
|
||||
@c['Content-Range'] = "bytes 1-500/1000"
|
||||
assert_equal 500, @c.range_length
|
||||
@c['Content-Range'] = "bytes 1-1/1000"
|
||||
assert_equal 1, @c.range_length
|
||||
end
|
||||
|
||||
def test_chunked?
|
||||
try_chunked true, 'chunked'
|
||||
try_chunked true, ' chunked '
|
||||
|
@ -110,28 +145,60 @@ class HTTPHeaderTest < Test::Unit::TestCase
|
|||
assert_equal len, @c.content_length
|
||||
end
|
||||
|
||||
def test_content_range
|
||||
def test_content_length=
|
||||
@c.content_length = 0
|
||||
assert_equal 0, @c.content_length
|
||||
@c.content_length = 1
|
||||
assert_equal 1, @c.content_length
|
||||
@c.content_length = 999
|
||||
assert_equal 999, @c.content_length
|
||||
@c.content_length = 10000000000000
|
||||
assert_equal 10000000000000, @c.content_length
|
||||
end
|
||||
|
||||
def test_delete
|
||||
def test_content_type
|
||||
@c.content_type = 'text/html'
|
||||
assert_equal 'text/html', @c.content_type
|
||||
@c.content_type = 'application/pdf'
|
||||
assert_equal 'application/pdf', @c.content_type
|
||||
@c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
|
||||
assert_equal 'text/html', @c.content_type
|
||||
end
|
||||
|
||||
def test_each
|
||||
def test_main_type
|
||||
@c.content_type = 'text/html'
|
||||
assert_equal 'text', @c.main_type
|
||||
@c.content_type = 'application/pdf'
|
||||
assert_equal 'application', @c.main_type
|
||||
@c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
|
||||
assert_equal 'text', @c.main_type
|
||||
end
|
||||
|
||||
def test_each_key
|
||||
def test_sub_type
|
||||
@c.content_type = 'text/html'
|
||||
assert_equal 'html', @c.sub_type
|
||||
@c.content_type = 'application/pdf'
|
||||
assert_equal 'pdf', @c.sub_type
|
||||
@c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
|
||||
assert_equal 'html', @c.sub_type
|
||||
end
|
||||
|
||||
def test_each_value
|
||||
def test_type_params
|
||||
@c.content_type = 'text/html'
|
||||
assert_equal({}, @c.type_params)
|
||||
@c.content_type = 'application/pdf'
|
||||
assert_equal({}, @c.type_params)
|
||||
@c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
|
||||
assert_equal({'charset' => 'iso-2022-jp'}, @c.type_params)
|
||||
end
|
||||
|
||||
def test_key?
|
||||
def test_set_content_type
|
||||
end
|
||||
|
||||
def test_range_length
|
||||
def test_basic_auth
|
||||
end
|
||||
|
||||
def test_to_hash
|
||||
def test_proxy_basic_auth
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue