Skip to content

[DOC] Enhanced RDoc for Net::HTTPHeader #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 23, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 250 additions & 50 deletions lib/net/http/header.rb
Original file line number Diff line number Diff line change
@@ -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 <tt>'Accept'</tt> is treated as if it were
# <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>.
# - A symbol: Key <tt>:Accept</tt> is treated as if it were
# <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>.
#
# 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
# <tt>self[key.downcase.to_s].join(', '))</tt>:
#
# - #[]: 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 <tt>'Content-Length'</tt>.
# - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>.
# - #content_type: Returns the string value of field <tt>'Content-Type'</tt>.
# - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>.
# - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>.
# - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+.
# - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>.
# - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>.
#
# === Setters
#
# - #[]=: Sets the string or array value for the given field.
# - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>.
# - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>.
# - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>.
# - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>.
# - #range=: Sets the value for field +'Range'+.
#
# === Queries
#
# - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>.
# - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>.
# - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>.
# - #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|
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -101,41 +253,65 @@ 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
return nil unless @header[stringified_downcased_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|
Expand All @@ -145,23 +321,42 @@ 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)
end

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+
Expand Down Expand Up @@ -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|
Expand Down Expand Up @@ -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
Expand All @@ -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 != '&'
Expand Down