Class: Protocol::HTTP::Headers

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/http/headers.rb

Overview

Headers are an array of key-value pairs. Some header keys represent multiple values.

Defined Under Namespace

Classes: Merged

Constant Summary collapse

Split =
Header::Split
Multiple =
Header::Multiple
TRAILERS =
'trailers'
POLICY =
{
	# Headers which may only be specified once.
	'content-type' => false,
	'content-disposition' => false,
	'content-length' => false,
	'user-agent' => false,
	'referer' => false,
	'host' => false,
	'authorization' => false,
	'proxy-authorization' => false,
	'if-modified-since' => false,
	'if-unmodified-since' => false,
	'from' => false,
	'location' => false,
	'max-forwards' => false,
	
	# Custom headers:
	'connection' => Header::Connection,
	'cache-control' => Header::CacheControl,
	'vary' => Header::Vary,
	
	# Headers specifically for proxies:
	'via' => Split,
	'x-forwarded-for' => Split,
	
	# 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,
}.tap{|hash| hash.default = Split}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fields = [], indexed = nil) ⇒ Headers

Returns a new instance of Headers.



50
51
52
53
54
55
56
57
# File 'lib/protocol/http/headers.rb', line 50

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

Instance Attribute Details

#fieldsObject (readonly)

An array of ‘[key, value]` pairs.



75
76
77
# File 'lib/protocol/http/headers.rb', line 75

def fields
  @fields
end

Class Method Details

.[](headers) ⇒ Headers

Construct an instance from a headers Array or Hash. No-op if already an instance of ‘Headers`.

Returns:

  • (Headers)

    an instance of headers.



42
43
44
45
46
47
48
# File 'lib/protocol/http/headers.rb', line 42

def self.[] headers
	if headers.is_a?(self)
		headers
	else
		self.new(headers.to_a)
	end
end

Instance Method Details

#==(other) ⇒ Object



300
301
302
303
304
305
306
307
308
309
# File 'lib/protocol/http/headers.rb', line 300

def == other
	case other
	when Hash
		to_h == other
	when Headers
		@fields == other.fields
	else
		@fields == other
	end
end

#[](key) ⇒ Object



283
284
285
# File 'lib/protocol/http/headers.rb', line 283

def [] key
	to_h[key]
end

#[]=(key, value) ⇒ Object

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

Parameters:

  • key (String)

    The header key.

  • value

    The header value.



198
199
200
201
202
203
204
# File 'lib/protocol/http/headers.rb', line 198

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

#add(key, value = nil) { ... } ⇒ Object

Add the specified header key value pair.

Parameters:

  • key (String)

    the header key.

  • value (String) (defaults to: nil)

    the header value to assign.

Yields:

  • dynamically generate the value when used as a trailer.



165
166
167
168
169
170
171
172
# File 'lib/protocol/http/headers.rb', line 165

def add(key, value = nil, &block)
	if block_given?
		@deferred << [key, block]
		self[TRAILERS] = key
	else
		self[key] = value
	end
end

#clearObject



67
68
69
70
71
72
# File 'lib/protocol/http/headers.rb', line 67

def clear
	@fields.clear
	@indexed = nil
	@tail = nil
	@deferred.clear
end

#delete(key) ⇒ Object

Delete all headers with the given key, and return the merged value.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/protocol/http/headers.rb', line 246

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

#each(&block) ⇒ Object



135
136
137
# File 'lib/protocol/http/headers.rb', line 135

def each(&block)
	@fields.each(&block)
end

#empty?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/protocol/http/headers.rb', line 131

def empty?
	@fields.empty?
end

#extract(keys) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/protocol/http/headers.rb', line 147

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

#flatten!Object



87
88
89
90
91
92
93
94
95
# File 'lib/protocol/http/headers.rb', line 87

def flatten!
	unless @deferred.empty?
		@tail ||= @fields.size
		
		@deferred.each do |key, value|
			self.add(key, value.call)
		end
	end
end

#freezeObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/protocol/http/headers.rb', line 110

def freeze
	return if frozen?
	
	# Ensure all deferred headers are evaluated:
	self.flatten!
	
	# Ensure @indexed is generated:
	self.to_h
	
	# Remove all trailers:
	self.delete(TRAILERS)
	
	# No longer has stateful trailers:
	@tail = nil
	
	@fields.freeze
	@indexed.freeze
	
	super
end

#include?(key) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/protocol/http/headers.rb', line 139

def include? key
	self[key] != nil
end

#initialize_dup(other) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/protocol/http/headers.rb', line 59

def initialize_dup(other)
	super
	
	@fields = @fields.dup
	@indexed = @indexed.dup
	@deferred = @deferred.dup
end

#inspectObject



296
297
298
# File 'lib/protocol/http/headers.rb', line 296

def inspect
	"#<#{self.class} #{@fields.inspect}>"
end

#keysObject



143
144
145
# File 'lib/protocol/http/headers.rb', line 143

def keys
	self.to_h.keys
end

#merge(headers) ⇒ Object



191
192
193
# File 'lib/protocol/http/headers.rb', line 191

def merge(headers)
	self.dup.merge!(headers)
end

#merge!(headers) ⇒ Object



183
184
185
186
187
188
189
# File 'lib/protocol/http/headers.rb', line 183

def merge!(headers)
	headers.each do |key, value|
		self[key] = value
	end
	
	return self
end

#set(key, value) ⇒ Object

Set the specified header key to the specified value, replacing any existing header keys with the same name.

Parameters:

  • key (String)

    the header key to replace.

  • value (String)

    the header value to assign.



177
178
179
180
181
# File 'lib/protocol/http/headers.rb', line 177

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

#to_hObject

A hash table of ‘policy.map(values)`



288
289
290
291
292
293
294
# File 'lib/protocol/http/headers.rb', line 288

def to_h
	@indexed ||= @fields.inject({}) do |hash, (key, value)|
		merge_into(hash, key.downcase, value)
		
		hash
	end
end

#trailers(&block) ⇒ Object

Enumerate all trailers, including evaluating all deferred headers.



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/protocol/http/headers.rb', line 98

def trailers(&block)
	return nil unless self.include?(TRAILERS)
	
	return to_enum(:trailers) unless block_given?
	
	flatten!
	
	if @tail
		@fields.drop(@tail).each(&block)
	end
end

#trailers!Object

Mark the subsequent headers as trailers.



78
79
80
# File 'lib/protocol/http/headers.rb', line 78

def trailers!
	@tail ||= @fields.size
end

#trailers?Boolean

Returns the trailers if there are any.

Returns:

  • (Boolean)

    the trailers if there are any.



83
84
85
# File 'lib/protocol/http/headers.rb', line 83

def trailers?
	@tail != nil
end