diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index b0ec4b06..a81196b9 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -1,16 +1,154 @@ # frozen_string_literal: false -# The HTTPHeader module defines methods for reading and writing -# HTTP headers. # -# It is used as a mixin by other classes, to provide hash-like -# access to HTTP header values. Unlike raw hash access, HTTPHeader -# provides access via case-insensitive keys. It also provides -# methods for accessing commonly-used HTTP header values in more -# convenient formats. +# The \HTTPHeader module provides access to \HTTP headers. +# The headers are a hash-like collection of key/value pairs called _fields_. +# +# The module is included in: +# +# - Net::HTTPGenericRequest (and therefore Net::HTTPRequest). +# - Net::HTTPResponse. +# +# == About the Examples +# +# :include: doc/net-http/examples.rdoc +# +# == Fields +# +# A header field is a key/value pair. +# +# === Field Keys +# +# A field key may be: +# +# - A string: Key 'Accept' is treated as if it were +# 'Accept'.downcase; i.e., 'accept'. +# - A symbol: Key :Accept is treated as if it were +# :Accept.to_s.downcase; i.e., 'accept'. +# +# Examples: +# +# req = Net::HTTP::Get.new(uri) +# req[:accept] # => "*/*" +# req['Accept'] # => "*/*" +# req['ACCEPT'] # => "*/*" +# +# req['accept'] = 'text/html' +# req[:accept] = 'text/html' +# req['ACCEPT'] = 'text/html' +# +# === Field Values +# +# A field value may be returned as an array of strings or as a string: +# +# - These methods return field values as arrays: +# +# - #get_fields: Returns the array value for the given key, +# or +nil+ if it does not exist. +# - #to_hash: Returns a hash of all header fields: +# each key is a field name; its value is the array value for the field. +# +# - These methods return field values as string; +# the string value for a field is equivalent to +# self[key.downcase.to_s].join(', ')): +# +# - #[]: Returns the string value for the given key, +# or +nil+ if it does not exist. +# - #fetch: Like #[], but accepts a default value +# to be returned if the key does not exist. +# +# The field value may be set: +# +# - #[]=: Sets the value for the given key; +# the given value may be a string, a symbol, an array, or a hash. +# - #add_field: Adds a given value to a value for the given key +# (not overwriting the existing value). +# - #delete: Deletes the field for the given key. +# +# Example field values: +# +# - \String: +# +# req['Accept'] = 'text/html' # => "text/html" +# req['Accept'] # => "text/html" +# req.get_fields('Accept') # => ["text/html"] +# +# - \Symbol: +# +# req['Accept'] = :text # => :text +# req['Accept'] # => "text" +# req.get_fields('Accept') # => ["text"] +# +# - Simple array: +# +# req[:foo] = %w[bar baz bat] +# req[:foo] # => "bar, baz, bat" +# req.get_fields(:foo) # => ["bar", "baz", "bat"] +# +# - Simple hash: +# +# req[:foo] = {bar: 0, baz: 1, bat: 2} +# req[:foo] # => "bar, 0, baz, 1, bat, 2" +# req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"] +# +# - Nested: +# +# req[:foo] = [%w[bar baz], {bat: 0, bam: 1}] +# req[:foo] # => "bar, baz, bat, 0, bam, 1" +# req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"] +# +# req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}} +# req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1" +# req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"] +# +# == Convenience Methods +# +# Various convenience methods retrieve values, set values, query values, +# set form values, or iterate over fields. +# +# === Getters +# +# - #[]: Returns the string value for the given field. +# - #content_length: Returns the integer value of field 'Content-Length'. +# - #content_range: Returns the Range value of field 'Content-Range'. +# - #content_type: Returns the string value of field 'Content-Type'. +# - #main_type: Returns first part of the string value of field 'Content-Type'. +# - #sub_type: Returns second part of the string value of field 'Content-Type'. +# - #range: Returns an array of Range objects of field 'Range', or +nil+. +# - #range_length: Returns the integer length of the range given in field 'Content-Range'. +# - #type_params: Returns the string parameters for 'Content-Type'. +# +# === Setters +# +# - #[]=: Sets the string or array value for the given field. +# - #basic_auth: Sets the string authorization header for 'Authorization'. +# - #content_length=: Sets the integer length for field 'Content-Length. +# - #content_type=: Sets the string value for field 'Content-Type'. +# - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. +# - #range=: Sets the value for field +'Range'+. +# +# === Queries +# +# - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. +# - #connection_close?: Returns whether field 'Connection' is set to 'close'. +# - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. +# - #key?: Returns whether a given field exists. +# +# === Form Setters +# +# - #set_form: Sets an HTML form data set. +# - #set_form_data: Sets header fields and a body from HTML form data. +# +# === Iterators +# +# - #each_capitalized: Passes each field capitalized-name/value pair to the block. +# - #each_capitalized_name: Passes each capitalized field name to the block. +# - #each_header: Passes each field name/value pair to the block. +# - #each_name: Passes each field name to the block. +# - #each_value: Passes each field value to the block. # module Net::HTTPHeader - def initialize_http_header(initheader) + def initialize_http_header(initheader) #:nodoc: @header = {} return unless initheader initheader.each do |key, value| @@ -33,14 +171,30 @@ def size #:nodoc: obsolete 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" + # Returns the string field value for the case-insensitive field +key+, + # or +nil+ if there is no such key; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # req = Net::HTTP::Get.new(uri) + # req['Accept'] # => "*/*" + # req['Foo'] = %w[bar baz bat] + # req['Foo'] # => "bar, baz, bat" + # res['Nosuch'] # => nil + # def [](key) a = @header[key.downcase.to_s] or return nil a.join(', ') end - # Sets the header field corresponding to the case-insensitive key. + # Sets the value for the case-insensitive +key+ to +val+, + # overwriting the previous value if the field exists; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # req = Net::HTTP::Get.new(uri) + # req['Accept'] # => "*/*" + # req['Accept'] = 'text/html' + # req['Accept'] # => "text/html" + # def []=(key, val) unless val @header.delete key.downcase.to_s @@ -49,20 +203,18 @@ def []=(key, val) set_field(key, val) end - # [Ruby 1.8.3] - # Adds a value to a named header field, instead of replacing its value. - # Second argument +val+ must be a String. - # See also #[]=, #[] and #get_fields. + # Adds value +val+ to the value array for field +key+ if the field exists; + # creates the field with the given +key+ and +val+ if it does not exist. + # see {Fields}[rdoc-ref:Net::HTTPHeader@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"] + # req = Net::HTTP::Get.new(uri) + # req.add_field('Foo', 'bar') + # req['Foo'] # => "bar" + # req.add_field('Foo', 'baz') + # req['Foo'] # => "bar, baz" + # req.add_field('Foo', %w[baz bam]) + # req['Foo'] # => "bar, baz, baz, bam" + # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"] # def add_field(key, val) stringified_downcased_key = key.downcase.to_s @@ -101,16 +253,16 @@ def add_field(key, val) end 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 #[]. + # Returns the array field value for the given +key+, + # or +nil+ if there is no such field; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # 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" + # req = Net::HTTP::Get.new(uri) + # req['Foo'] = 'bar' + # req.get_fields('Foo') # => ["bar"] + # req.add_field('Foo', 'baz') + # req.get_fields('Foo') # => ["bar", "baz"] + # req.get_fields('Nosuch') # => nil # def get_fields(key) stringified_downcased_key = key.downcase.to_s @@ -118,24 +270,48 @@ def get_fields(key) @header[stringified_downcased_key].dup end - # Returns the header field corresponding to the case-insensitive key. - # Returns the default value +args+, or the result of the block, or - # raises an IndexError if there's no header field named +key+ - # See Hash#fetch + # :call-seq + # fetch(key, default_val = nil) {|key| ... } -> object + # fetch(key, default_val = nil) -> value or default_val + # + # With a block, returns the string value for +key+ if it exists; + # otherwise returns the value of the block; + # ignores the +default_val+; + # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: + # + # req = Net::HTTP::Get.new(uri) + # req['Foo'] = 'bar' + # req.fetch('Foo') {|key| key.capitalize } # => "bar" + # req.fetch('Nosuch') {|key| key.capitalize } # => "Nosuch" + # + # With no block, returns the string value for +key+ if it exists; + # otherwise, returns +default_val+ if it was given; + # otherwise raises an exception: + # + # req.fetch('Foo') # => "bar" + # req.fetch('Nosuch', :baz) # => :baz + # req.fetch('Nosuch') # Raises KeyError. + # def fetch(key, *args, &block) #:yield: +key+ a = @header.fetch(key.downcase.to_s, *args, &block) a.kind_of?(Array) ? a.join(', ') : a end - # Iterates through the header names and values, passing in the name - # and value to the code block supplied. + # Calls the block with each key/value pair: # - # Returns an enumerator if no block is given. + # req = Net::HTTP::Get.new(uri) + # req.each_header {|key, value| p [key, value] } # - # Example: + # Output: # - # response.header.each_header {|key,value| puts "#{key} = #{value}" } + # ["accept-encoding", "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"] + # ["accept", "*/*"] + # ["user-agent", "Ruby"] + # ["host", "jsonplaceholder.typicode.com"] + # + # Returns an enumerator if no block is given. # + # Net::HTTPHeader#each is an alias for Net::HTTPHeader#each_header. def each_header #:yield: +key+, +value+ block_given? or return enum_for(__method__) { @header.size } @header.each do |k,va| @@ -145,10 +321,21 @@ def each_header #:yield: +key+, +value+ alias each each_header - # Iterates through the header names in the header, passing - # each header name to the code block. + # Calls the block with each field key: + # + # req = Net::HTTP::Get.new(uri) + # req.each_key {|key| p key } + # + # Output: + # + # "accept-encoding" + # "accept" + # "user-agent" + # "host" # # Returns an enumerator if no block is given. + # + # Net::HTTPHeader#each_name is an alias for Net::HTTPHeader#each_key. def each_name(&block) #:yield: +key+ block_given? or return enum_for(__method__) { @header.size } @header.each_key(&block) @@ -156,12 +343,20 @@ def each_name(&block) #:yield: +key+ alias each_key each_name - # Iterates through the header names in the header, passing - # capitalized header names to the code block. + # Calls the block with each capitalized field name: # - # Note that header names are capitalized systematically; - # capitalization may not match that used by the remote HTTP - # server in its response. + # req = Net::HTTP::Get.new(uri) + # req.each_capitalized_name {|key| p key } + # + # Output: + # + # "Accept-Encoding" + # "Accept" + # "User-Agent" + # "Host" + # + # The capitalization is system-dependent; + # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html]. # # Returns an enumerator if no block is given. def each_capitalized_name #:yield: +key+ @@ -208,6 +403,8 @@ def to_hash # server in its response. # # Returns an enumerator if no block is given. + # + # Net::HTTPHeader#canonical_each is an alias for Net::HTTPHeader#each_capitalized. def each_capitalized block_given? or return enum_for(__method__) { @header.size } @header.each do |k,v| @@ -394,7 +591,9 @@ def type_params # Sets the content type in an HTTP header. # The +type+ should be a full HTTP content type, e.g. "text/html". # The +params+ are an optional Hash of parameters to add after the - # content type, e.g. {'charset' => 'iso-8859-1'} + # content type, e.g. {'charset' => 'iso-8859-1'}. + # + # Net::HTTPHeader#content_type= is an alias for Net::HTTPHeader#set_content_type. def set_content_type(type, params = {}) @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] end @@ -414,6 +613,7 @@ def set_content_type(type, params = {}) # http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"} # http.set_form_data({"q" => "ruby", "lang" => "en"}, ';') # + # Net::HTTPHeader#form_data= is an alias for Net::HTTPHeader#set_form_data. def set_form_data(params, sep = '&') query = URI.encode_www_form(params) query.gsub!(/&/, sep) if sep != '&'