Protocol::HTTPSourceProtocolHTTPHeaders

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 key String

The header key.

returns String | Array | Object

The header value.

Implementation

def [] key
	to_h[key]
end

def initialize(fields = [], indexed = nil)

Initialize the headers with the specified fields.

Signature

parameter fields Array

An array of [key, value] pairs.

parameter indexed Hash

A hash table of normalized headers, if available.

Implementation

def initialize(fields = [], indexed = nil)
	@fields = fields
	@indexed = indexed
	
	# Marks where trailer start in the @fields array.
	@tail = nil
end

def initialize_dup(other)

Initialize a copy of the headers.

Signature

parameter other Headers

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
	@indexed = nil
	@tail = 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.

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 names Array

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 trailer(&block)

Enumerate all headers in the trailer, if there are any.

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 key String

The header key.

parameter value String

The header value.

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 keys Array

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)

Add the specified header key value pair.

Signature

parameter key String

the header key.

parameter value String

the header value to assign.

Implementation

def add(key, value)
	self[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 key String

the header key to replace.

parameter value String

the header value to assign.

Implementation

def set(key, value)
	# TODO This could be a bit more efficient:
	self.delete(key)
	self.add(key, value)
end

def merge!(headers)

Merge the headers into this instance.

Implementation

def merge!(headers)
	headers.each do |key, value|
		self[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

def []= key, value

Append the value to the given key. Some values can be appended multiple times, others can only be set once.

Signature

parameter key String

The header key.

parameter value String

The header value.

Implementation

def []= key, value
	if @indexed
		merge_into(@indexed, key.downcase, value)
	end
	
	@fields << [key, value]
end

POLICY

The policy for various headers, including how they are merged and normalized.

Implementation

POLICY = {
	# Headers which may only be specified once:
	"content-type" => false,
	"content-disposition" => false,
	"content-length" => false,
	"user-agent" => false,
	"referer" => false,
	"host" => false,
	"from" => false,
	"location" => false,
	"max-forwards" => false,
	"retry-after" => false,
	
	# Custom headers:
	"connection" => Header::Connection,
	"cache-control" => Header::CacheControl,
	"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,
	
	# 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,
}.tap{|hash| hash.default = Split}

def delete(key)

Delete all header values for the given key, and return the merged value.

Signature

parameter key String

The header key.

returns String | Array | Object

The merged header value.

Implementation

def delete(key)
	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.new(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.

Signature

returns Hash

A hash table of {key, value} pairs.

Implementation

def to_h
	@indexed ||= @fields.inject({}) do |hash, (key, value)|
		merge_into(hash, key.downcase, value)
		
		hash
	end
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
		to_h == other
	when Headers
		@fields == other.fields
	else
		@fields == other
	end
end