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
TRAILER =
"trailer"
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.

{
	# 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" => Header::Range,
	"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}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fields = [], tail = nil, indexed: nil, policy: POLICY) ⇒ Headers

Initialize the headers with the specified fields.



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/protocol/http/headers.rb', line 76

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

Instance Attribute Details

#An array of `[key, value]` pairs.(arrayof`[key, value]`pairs.) ⇒ Object (readonly)



134
# File 'lib/protocol/http/headers.rb', line 134

attr :fields

#fieldsObject (readonly)

Returns the value of attribute fields.



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

def fields
  @fields
end

#policyObject

Returns the value of attribute policy.



89
90
91
# File 'lib/protocol/http/headers.rb', line 89

def policy
  @policy
end

#tailObject (readonly)

Returns the value of attribute tail.



137
138
139
# File 'lib/protocol/http/headers.rb', line 137

def tail
  @tail
end

#The index where trailers begin.(indexwheretrailers) ⇒ Object (readonly)



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

attr :tail

Class Method Details

.[](headers) ⇒ 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.

Returns:

  • (Headers)

    an instance of headers.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/protocol/http/headers.rb', line 50

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

Instance Method Details

#==(other) ⇒ Object

Compare this object to another object. May depend on the order of the fields.



503
504
505
506
507
508
509
510
511
512
# File 'lib/protocol/http/headers.rb', line 503

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

#[](key) ⇒ Object

Get the value of the specified header key.



318
319
320
# File 'lib/protocol/http/headers.rb', line 318

def [] key
	self.to_h[key]
end

#[]=(key, value) ⇒ Object

Set the specified header key to the specified value, replacing any existing values.

The value can be a String or a coercable value.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/protocol/http/headers.rb', line 286

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
	
	if value.is_a?(Multiple)
		value.each do |v|
			@fields << [key, v.to_s]
		end
	else
		@fields << [key, value.to_s]
	end
end

#add(key, value, trailer: self.trailer?) ⇒ Object

Add the specified header key value pair.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/protocol/http/headers.rb', line 253

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

#clearObject

Clear all headers.



112
113
114
115
116
# File 'lib/protocol/http/headers.rb', line 112

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

#delete(key) ⇒ Object

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



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/protocol/http/headers.rb', line 421

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

#each(&block) ⇒ Object

Enumerate all header keys and values.



215
216
217
# File 'lib/protocol/http/headers.rb', line 215

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

#empty?Boolean

Returns:

  • (Boolean)


206
207
208
# File 'lib/protocol/http/headers.rb', line 206

def empty?
	@fields.empty?
end

#extract(keys) ⇒ Object

Extract the specified keys from the headers.



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/protocol/http/headers.rb', line 234

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

#flattenObject

Flatten trailer into the headers, returning a new instance of Protocol::HTTP::Headers.



129
130
131
# File 'lib/protocol/http/headers.rb', line 129

def flatten
	self.dup.flatten!
end

#flatten!Object

Flatten trailer into the headers, in-place.



119
120
121
122
123
124
125
126
# File 'lib/protocol/http/headers.rb', line 119

def flatten!
	if @tail
		self.delete(TRAILER)
		@tail = nil
	end
	
	return self
end

#freezeObject

Freeze the headers, and ensure the indexed hash is generated.



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/protocol/http/headers.rb', line 193

def freeze
	return if frozen?
	
	# Ensure @indexed is generated:
	self.to_h
	
	@fields.freeze
	@indexed.freeze
	
	super
end

#header(&block) ⇒ Object

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



169
170
171
172
173
174
175
176
177
# File 'lib/protocol/http/headers.rb', line 169

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

#include?(key) ⇒ Boolean Also known as: key?

Returns:

  • (Boolean)


220
221
222
# File 'lib/protocol/http/headers.rb', line 220

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

#initialize_dup(other) ⇒ Object

Initialize a copy of the headers.



104
105
106
107
108
109
# File 'lib/protocol/http/headers.rb', line 104

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

#inspectObject

Inspect the headers.



496
497
498
# File 'lib/protocol/http/headers.rb', line 496

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

#keysObject



227
228
229
# File 'lib/protocol/http/headers.rb', line 227

def keys
	self.to_h.keys
end

#merge(headers) ⇒ Object

Merge the headers into a new instance of Protocol::HTTP::Headers.



332
333
334
# File 'lib/protocol/http/headers.rb', line 332

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

#merge!(headers) ⇒ Object

Merge the headers into this instance.



323
324
325
326
327
328
329
# File 'lib/protocol/http/headers.rb', line 323

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



275
276
277
278
# File 'lib/protocol/http/headers.rb', line 275

def set(key, value)
	self.delete(key)
	self.add(key, value)
end

#The policy for the headers.=(policy) ⇒ Object



89
# File 'lib/protocol/http/headers.rb', line 89

attr :policy

#to_aObject



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

def to_a
	@fields
end

#to_hObject Also known as: as_json

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.



476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/protocol/http/headers.rb', line 476

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

#trailer(&block) ⇒ Object

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



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

def trailer(&block)
	return to_enum(:trailer) unless block_given?
	
	if @tail
		@fields.drop(@tail).each(&block)
	end
end

#trailer!(&block) ⇒ Object

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.



158
159
160
161
162
# File 'lib/protocol/http/headers.rb', line 158

def trailer!(&block)
	@tail ||= @fields.size
	
	return trailer(&block)
end

#trailer?Boolean

Returns:

  • (Boolean)


145
146
147
# File 'lib/protocol/http/headers.rb', line 145

def trailer?
	@tail != nil
end