lib/protocol/http/headers.rb in protocol-http-0.16.2 vs lib/protocol/http/headers.rb in protocol-http-0.16.3
- old
+ new
@@ -23,56 +23,107 @@
require_relative 'header/split'
require_relative 'header/multiple'
require_relative 'header/cookie'
require_relative 'header/connection'
require_relative 'header/cache_control'
+require_relative 'header/etag'
require_relative 'header/etags'
require_relative 'header/vary'
module Protocol
module HTTP
# Headers are an array of key-value pairs. Some header keys represent multiple values.
class Headers
Split = Header::Split
Multiple = Header::Multiple
+ TRAILERS = 'trailers'
- def self.[] hash
- self.new(hash.to_a)
- end
-
- def initialize(fields = nil, indexed = nil)
- if fields
- @fields = fields.dup
+ # Construct an instance from a headers Array or Hash. No-op if already an instance of `Headers`.
+ # @return [Headers] an instance of headers.
+ def self.[] headers
+ if headers.is_a?(self)
+ headers
else
- @fields = []
+ self.new(headers.to_a)
end
+ end
+
+ def initialize(fields = [], indexed = nil)
+ @fields = fields
+ @indexed = indexed
- if indexed
- @indexed = indexed.dup
- else
- @indexed = nil
- end
+ # Marks where trailers start in the @fields array.
+ @tail = nil
+ @deferred = []
end
- def dup
- self.class.new(@fields, @indexed)
+ def initialize_dup(other)
+ super
+
+ @fields = @fields.dup
+ @indexed = @indexed.dup
+ @deferred = @deferred.dup
end
def clear
@fields.clear
@indexed = nil
+ @tail = nil
+ @deferred.clear
end
# An array of `[key, value]` pairs.
attr :fields
+ # Mark the subsequent headers as trailers.
+ def trailers!
+ @tail ||= @fields.size
+ end
+
+ # @return the trailers if there are any.
+ def trailers?
+ @tail != nil
+ end
+
+ def flatten!
+ unless @deferred.empty?
+ @tail ||= @fields.size
+
+ @deferred.each do |key, value|
+ self.add(key, value.call)
+ end
+ end
+ end
+
+ # Enumerate all trailers, including evaluating all deferred headers.
+ 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
+
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
@@ -87,10 +138,14 @@
def include? key
self[key] != nil
end
+ def keys
+ self.to_h.keys
+ end
+
def extract(keys)
deleted, @fields = @fields.partition do |field|
keys.include?(field.first.downcase)
end
@@ -101,18 +156,21 @@
end
return deleted
end
- # This is deprecated.
- alias slice! extract
-
# Add the specified header key value pair.
# @param key [String] the header key.
# @param value [String] the header value to assign.
- def add(key, value)
- self[key] = value
+ # @yield dynamically generate the value when used as a trailer.
+ def add(key, value = nil, &block)
+ if block_given?
+ @deferred << [key, block]
+ self[TRAILERS] = key
+ else
+ self[key] = value
+ end
end
# Set the specified header key to the specified value, replacing any existing header keys with the same name.
# @param key [String] the header key to replace.
# @param value [String] the header value to assign.
@@ -143,11 +201,11 @@
end
@fields << [key, value]
end
- MERGE_POLICY = {
+ POLICY = {
# Headers which may only be specified once.
'content-type' => false,
'content-disposition' => false,
'content-length' => false,
'user-agent' => false,
@@ -159,19 +217,21 @@
'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,
@@ -192,11 +252,11 @@
return nil
end
if @indexed
return @indexed.delete(key)
- elsif policy = MERGE_POLICY[key]
+ elsif policy = POLICY[key]
(key, value), *tail = deleted
merged = policy.new(value)
tail.each{|k,v| merged << v}
@@ -206,10 +266,10 @@
return value
end
end
protected def merge_into(hash, key, value)
- if policy = MERGE_POLICY[key]
+ if policy = POLICY[key]
if current_value = hash[key]
current_value << value
else
hash[key] = policy.new(value)
end