class Headers
Headers are an array of key-value pairs. Some header keys represent multiple values.
Nested
Definitions
def self.[](headers)
Construct an instance from a headers Array or Hash. No-op if already an instance of Headers. If the underlying array is frozen, it will be duped.
Implementation
def self.[] headers
if headers.nil?
return self.new
end
if headers.is_a?(self)
if headers.frozen?
return headers.dup
else
return headers
end
end
fields = headers.to_a
if fields.frozen?
fields = fields.dup
end
return self.new(fields)
end
def [](key)
Get the value of the specified header key.
Signature
-
parameter
keyString The header key.
-
returns
String | Array | Object The header value.
Implementation
def [] key
self.to_h[key]
end
def initialize(fields = [], tail = nil, indexed: nil, policy: POLICY)
Initialize the headers with the specified fields.
Signature
-
parameter
fieldsArray An array of
[key, value]pairs.-
parameter
tailInteger | Nil The index of the trailer start in the @fields array.
Implementation
def initialize(fields = [], tail = nil, indexed: nil, policy: POLICY)
@fields = fields
# Marks where trailer start in the @fields array:
@tail = tail
# The cached index of headers:
@indexed = nil
@policy = policy
end
attr :policy
Signature
-
attribute
Hash The policy for the headers.
def policy=(policy)
Set the policy for the headers.
The policy is used to determine how headers are merged and normalized. For example, if a header is specified multiple times, the policy will determine how the values are merged.
Signature
-
parameter
policyHash The policy for the headers.
Implementation
def policy=(policy)
@policy = policy
@indexed = nil
end
def initialize_dup(other)
Initialize a copy of the headers.
Signature
-
parameter
otherHeaders The headers to copy.
Implementation
def initialize_dup(other)
super
@fields = @fields.dup
@indexed = @indexed.dup
end
def clear
Clear all headers.
Implementation
def clear
@fields.clear
@tail = nil
@indexed = nil
end
def flatten!
Flatten trailer into the headers, in-place.
Implementation
def flatten!
if @tail
self.delete(TRAILER)
@tail = nil
end
return self
end
def flatten
Flatten trailer into the headers, returning a new instance of class Protocol::HTTP::Headers.
Implementation
def flatten
self.dup.flatten!
end
attr :fields
Signature
-
attribute
Array An array of
[key, value]pairs.
attr :tail
Signature
-
attribute
Integer | Nil The index where trailers begin.
def to_a
Signature
-
returns
Array The fields of the headers.
Implementation
def to_a
@fields
end
def trailer?
Signature
-
returns
Boolean Whether there are any trailers.
Implementation
def trailer?
@tail != nil
end
def trailer!(&block)
Record the current headers, and prepare to add trailers.
This method is typically used after headers are sent to capture any additional headers which should then be sent as trailers.
A sender that intends to generate one or more trailer fields in a message should generate a trailer header field in the header section of that message to indicate which fields might be present in the trailers.
Signature
-
parameter
namesArray The trailer header names which will be added later.
-
yields
{|name, value| ...} the trailing headers if a block is given.
Implementation
def trailer!(&block)
@tail ||= @fields.size
return trailer(&block)
end
def header(&block)
Enumerate all the headers in the header, if there are any.
Signature
-
yields
{|key, value| ...} The header key and value.
-
parameter
keyString The header key.
-
parameter
valueString The raw header value.
-
parameter
Implementation
def header(&block)
return to_enum(:header) unless block_given?
if @tail and @tail < @fields.size
@fields.first(@tail).each(&block)
else
@fields.each(&block)
end
end
def trailer(&block)
Enumerate all headers in the trailer, if there are any.
Signature
-
yields
{|key, value| ...} The header key and value.
-
parameter
keyString The header key.
-
parameter
valueString The raw header value.
-
parameter
Implementation
def trailer(&block)
return to_enum(:trailer) unless block_given?
if @tail
@fields.drop(@tail).each(&block)
end
end
def freeze
Freeze the headers, and ensure the indexed hash is generated.
Implementation
def freeze
return if frozen?
# Ensure @indexed is generated:
self.to_h
@fields.freeze
@indexed.freeze
super
end
def empty?
Signature
-
returns
Boolean Whether the headers are empty.
Implementation
def empty?
@fields.empty?
end
def each(&block)
Enumerate all header keys and values.
Signature
-
yields
{|key, value| ...} -
parameter
keyString The header key.
-
parameter
valueString The raw header value.
-
parameter
Implementation
def each(&block)
@fields.each(&block)
end
def include?(key)
Signature
-
returns
Boolean Whether the headers include the specified key.
Implementation
def include? key
self[key] != nil
end
def keys
Signature
-
returns
Array All the keys of the headers.
Implementation
def keys
self.to_h.keys
end
def extract(keys)
Extract the specified keys from the headers.
Signature
-
parameter
keysArray The keys to extract.
Implementation
def extract(keys)
deleted, @fields = @fields.partition do |field|
keys.include?(field.first.downcase)
end
if @indexed
keys.each do |key|
@indexed.delete(key)
end
end
return deleted
end
def add(key, value, trailer: self.trailer?)
Add the specified header key value pair.
Signature
-
parameter
keyString the header key.
-
parameter
valueString the header value to assign.
-
parameter
trailerBoolean whether this header is being added as a trailer.
Implementation
def add(key, value, trailer: self.trailer?)
value = value.to_s
if trailer
policy = @policy[key.downcase]
if !policy or !policy.trailer?
raise InvalidTrailerError, key
end
end
if @indexed
merge_into(@indexed, key.downcase, value)
end
@fields << [key, value]
end
def set(key, value)
Set the specified header key to the specified value, replacing any existing header keys with the same name.
Signature
-
parameter
keyString the header key to replace.
-
parameter
valueString the header value to assign.
Implementation
def set(key, value)
self.delete(key)
self.add(key, value)
end
def []=(key, value)
Set the specified header key to the specified value, replacing any existing values.
The value can be a String or a coercable value.
Signature
-
parameter
keyString the header key.
-
parameter
valueString | Array the header value to assign.
Implementation
def []=(key, value)
key = key.downcase
# Delete existing value if any:
self.delete(key)
if policy = @policy[key]
unless value.is_a?(policy)
value = policy.coerce(value)
end
else
value = value.to_s
end
# Clear the indexed cache so it will be rebuilt with parsed values when accessed:
if @indexed
@indexed[key] = value
end
@fields << [key, value.to_s]
end
def merge!(headers)
Merge the headers into this instance.
Implementation
def merge!(headers)
headers.each do |key, value|
self.add(key, value)
end
return self
end
def merge(headers)
Merge the headers into a new instance of class Protocol::HTTP::Headers.
Implementation
def merge(headers)
self.dup.merge!(headers)
end
POLICY
The policy for various headers, including how they are merged and normalized.
A policy may be false to indicate that the header may only be specified once and is a simple string.
Otherwise, the policy is a class which implements the header normalization logic, including parse and coerce class methods.
Implementation
POLICY = {
# Headers which may only be specified once:
"content-disposition" => false,
"content-length" => false,
"content-type" => false,
"expect" => false,
"from" => false,
"host" => false,
"location" => false,
"max-forwards" => false,
"range" => false,
"referer" => false,
"retry-after" => false,
"server" => false,
"transfer-encoding" => Header::TransferEncoding,
"user-agent" => false,
"trailer" => Header::Trailer,
# Connection handling:
"connection" => Header::Connection,
"upgrade" => Header::Split,
# Cache handling:
"cache-control" => Header::CacheControl,
"te" => Header::TE,
"vary" => Header::Vary,
"priority" => Header::Priority,
# Headers specifically for proxies:
"via" => Split,
"x-forwarded-for" => Split,
# Authorization headers:
"authorization" => Header::Authorization,
"proxy-authorization" => Header::Authorization,
# Cache validations:
"etag" => Header::ETag,
"if-match" => Header::ETags,
"if-none-match" => Header::ETags,
"if-range" => false,
# Headers which may be specified multiple times, but which can't be concatenated:
"www-authenticate" => Multiple,
"proxy-authenticate" => Multiple,
# Custom headers:
"set-cookie" => Header::SetCookie,
"cookie" => Header::Cookie,
# Date headers:
# These headers include a comma as part of the formatting so they can't be concatenated.
"date" => Header::Date,
"expires" => Header::Date,
"last-modified" => Header::Date,
"if-modified-since" => Header::Date,
"if-unmodified-since" => Header::Date,
# Accept headers:
"accept" => Header::Accept,
"accept-ranges" => Header::Split,
"accept-charset" => Header::AcceptCharset,
"accept-encoding" => Header::AcceptEncoding,
"accept-language" => Header::AcceptLanguage,
# Content negotiation headers:
"content-encoding" => Header::Split,
"content-range" => false,
# Performance headers:
"server-timing" => Header::ServerTiming,
# Content integrity headers:
"digest" => Header::Digest,
}.tap{|hash| hash.default = Header::Generic}
def delete(key)
Delete all header values for the given key, and return the merged value.
Signature
-
parameter
keyString The header key.
-
returns
String | Array | Object The merged header value.
Implementation
def delete(key)
# If we've indexed the headers, we can bail out early if the key is not present:
if @indexed && !@indexed.key?(key.downcase)
return nil
end
deleted, @fields = @fields.partition do |field|
field.first.downcase == key
end
if deleted.empty?
return nil
end
if @indexed
return @indexed.delete(key)
elsif policy = @policy[key]
(key, value), *tail = deleted
merged = policy.parse(value)
tail.each{|k,v| merged << v}
return merged
else
key, value = deleted.last
return value
end
end
def to_h
Compute a hash table of headers, where the keys are normalized to lower case and the values are normalized according to the policy for that header.
This will enforce policy rules, such as merging multiple headers into arrays, or raising errors for duplicate headers.
Signature
-
returns
Hash A hash table of
{key, value}pairs.
Implementation
def to_h
unless @indexed
indexed = {}
@fields.each do |key, value|
merge_into(indexed, key.downcase, value)
end
# Deferred assignment so that exceptions in `merge_into` don't leave us in an inconsistent state:
@indexed = indexed
end
return @indexed
end
def inspect
Inspect the headers.
Signature
-
returns
String A string representation of the headers.
Implementation
def inspect
"#<#{self.class} #{@fields.inspect}>"
end
def ==(other)
Compare this object to another object. May depend on the order of the fields.
Signature
-
returns
Boolean Whether the other object is equal to this one.
Implementation
def == other
case other
when Hash
self.to_h == other
when Headers
@fields == other.fields
else
@fields == other
end
end