Skip to content

Commit

Permalink
Unify work with headers across request/response
Browse files Browse the repository at this point in the history
  • Loading branch information
ixti committed Feb 11, 2014
1 parent 6400a2e commit a8d29d1
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -22,7 +22,7 @@ end

require 'yardstick/rake/verify'
Yardstick::Rake::Verify.new do |verify|
verify.threshold = 56.8
verify.threshold = 56.7
end

task :default => [:spec, :rubocop, :verify_measurements]
11 changes: 0 additions & 11 deletions lib/http/header.rb

This file was deleted.

55 changes: 55 additions & 0 deletions lib/http/headers.rb
@@ -0,0 +1,55 @@
require 'forwardable'
require 'delegate'

module HTTP
# Headers Hash wraper with keys normalization
class Headers < ::Delegator
module Mixin
extend Forwardable
attr_reader :headers
def_delegators :headers, :[], :[]=
end

# Matches HTTP header names when in "Canonical-Http-Format"
CANONICAL_HEADER = /^[A-Z][a-z]*(-[A-Z][a-z]*)*$/

def initialize(obj = {})
super({})
__setobj__ obj
end

# Transform to canonical HTTP header capitalization
def canonicalize_header(header)
header.to_s.split(/[\-_]/).map(&:capitalize).join('-')
end

# Obtain the given header
def [](name)
super(name) || super(canonicalize_header name)
end

# Set a header
def []=(name, value)
# If we have a canonical header, we're done, canonicalize otherwise
name = name.to_s[CANONICAL_HEADER] || canonicalize_header(name)

# Check if the header has already been set and group
value = Array(self[name]) + Array(value) if key? name

super name, value
end

protected

# :nodoc:
def __getobj__
@headers
end

# :nodoc:
def __setobj__(obj)
@headers = {}
obj.each { |k, v| self[k] = v } if obj.respond_to? :each
end
end
end
27 changes: 8 additions & 19 deletions lib/http/request.rb
@@ -1,11 +1,11 @@
require 'http/header'
require 'http/headers'
require 'http/request/writer'
require 'uri'
require 'base64'

module HTTP
class Request
include HTTP::Header
include HTTP::Headers::Mixin

# The method given was not understood
class UnsupportedMethodError < RequestError; end
Expand Down Expand Up @@ -57,7 +57,7 @@ def method(*args)
# "Request URI" as per RFC 2616
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
attr_reader :uri
attr_reader :headers, :proxy, :body, :version
attr_reader :proxy, :body, :version

# :nodoc:
def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = '1.1') # rubocop:disable ParameterLists
Expand All @@ -68,16 +68,10 @@ def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = '1.1')
fail(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(verb)
fail(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(scheme)

@headers = {}
headers.each do |name, value|
name = name.to_s
key = name[CANONICAL_HEADER]
key ||= canonicalize_header(name)
@headers[key] = value
end
@headers['Host'] ||= @uri.host

@proxy, @body, @version = proxy, body, version

@headers = HTTP::Headers.new(headers)
@headers['Host'] ||= @uri.host
end

# Returns new Request with updated uri
Expand All @@ -88,15 +82,10 @@ def redirect(uri)
req
end

# Obtain the given header
def [](header)
@headers[canonicalize_header(header)]
end

# Stream the request to a socket
def stream(socket)
include_proxy_authorization_header if using_authenticated_proxy?
Request::Writer.new(socket, body, @headers, request_header).stream
Request::Writer.new(socket, body, headers, request_header).stream
end

# Is this request using a proxy?
Expand All @@ -112,7 +101,7 @@ def using_authenticated_proxy?
# Compute and add the Proxy-Authorization header
def include_proxy_authorization_header
digest = Base64.encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}").chomp
@headers['Proxy-Authorization'] = "Basic #{digest}"
headers['Proxy-Authorization'] = "Basic #{digest}"
end

# Compute HTTP request header for direct or proxy request
Expand Down
34 changes: 5 additions & 29 deletions lib/http/response.rb
@@ -1,10 +1,10 @@
require 'delegate'
require 'http/header'
require 'http/headers'
require 'http/content_type'

module HTTP
class Response
include HTTP::Header
include HTTP::Headers::Mixin

STATUS_CODES = {
100 => 'Continue',
Expand Down Expand Up @@ -66,7 +66,6 @@ class Response
SYMBOL_TO_STATUS_CODE.freeze

attr_reader :status
attr_reader :headers
attr_reader :body
attr_reader :uri

Expand All @@ -76,37 +75,14 @@ class Response

def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists
@status, @version, @body, @uri = status, version, body, uri

@headers = {}
headers.each { |field, value| self[field] = value }
end

# Set a header
def []=(name, value)
# If we have a canonical header, we're done
key = name[CANONICAL_HEADER]

# Convert to canonical capitalization
key ||= canonicalize_header(name)

# Check if the header has already been set and group
if @headers.key? key
@headers[key] = Array(@headers[key]) + Array(value)
else
@headers[key] = value
end
@headers = HTTP::Headers.new(headers)
end

# Obtain the 'Reason-Phrase' for the response
def reason
STATUS_CODES[@status]
end

# Get a header value
def [](name)
@headers[name] || @headers[canonicalize_header(name)]
end

# Returns an Array ala Rack: `[status, headers, body]`
def to_a
[status, headers, body.to_s]
Expand All @@ -121,7 +97,7 @@ def to_s
# Parsed Content-Type header
# @return [HTTP::ContentType]
def content_type
@content_type ||= ContentType.parse @headers['Content-Type']
@content_type ||= ContentType.parse headers['Content-Type']
end

# MIME type of response (if any)
Expand All @@ -138,7 +114,7 @@ def charset

# Inspect a response
def inspect
"#<#{self.class}/#{@version} #{status} #{reason} @headers=#{@headers.inspect}>"
"#<#{self.class}/#{@version} #{status} #{reason} headers=#{headers.inspect}>"
end

class BodyDelegator < ::Delegator
Expand Down

0 comments on commit a8d29d1

Please sign in to comment.