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" => 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}

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.



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

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)



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

attr :fields

#fieldsObject (readonly)

Returns the value of attribute fields.



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

def fields
  @fields
end

#policyObject

Returns the value of attribute policy.



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

def policy
  @policy
end

#tailObject (readonly)

Returns the value of attribute tail.



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

def tail
  @tail
end

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



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

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.



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

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.



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

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.



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

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.



285
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
# File 'lib/protocol/http/headers.rb', line 285

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.



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

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.



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

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.



420
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
# File 'lib/protocol/http/headers.rb', line 420

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.



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

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

#empty?Boolean

Returns:

  • (Boolean)


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

def empty?
	@fields.empty?
end

#extract(keys) ⇒ Object

Extract the specified keys from the headers.



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

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.



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

def flatten
	self.dup.flatten!
end

#flatten!Object

Flatten trailer into the headers, in-place.



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

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

#freezeObject

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



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

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.



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

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)


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

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

#initialize_dup(other) ⇒ Object

Initialize a copy of the headers.



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

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

#inspectObject

Inspect the headers.



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

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

#keysObject



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

def keys
	self.to_h.keys
end

#merge(headers) ⇒ Object

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



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

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

#merge!(headers) ⇒ Object

Merge the headers into this instance.



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

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.



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

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

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



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

attr :policy

#to_aObject



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

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.



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

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.



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

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.



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

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

#trailer?Boolean

Returns:

  • (Boolean)


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

def trailer?
	@tail != nil
end