Skip to content

Commit

Permalink
dont depend on rack version for public api (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcai authored Feb 16, 2024
1 parent 906621c commit ec5fd17
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 6 deletions.
1 change: 1 addition & 0 deletions lib/httpi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "httpi/logger"
require "httpi/request"
require "httpi/query_builder"
require "httpi/utils"

require "httpi/adapter/httpclient"
require "httpi/adapter/curb"
Expand Down
4 changes: 2 additions & 2 deletions lib/httpi/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ def ssl?

# Returns a Hash of HTTP headers. Defaults to return an empty Hash.
def headers
@headers ||= Rack::Headers.new
@headers ||= HTTPI::Utils::Headers.new
end

# Sets the Hash of HTTP headers.
def headers=(headers)
@headers = Rack::Headers.new.merge(headers)
@headers = HTTPI::Utils::Headers.new.merge(headers)
end

# Adds a header information to accept gzipped content.
Expand Down
2 changes: 1 addition & 1 deletion lib/httpi/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Response
# Initializer expects an HTTP response +code+, +headers+ and +body+.
def initialize(code, headers, body)
self.code = code.to_i
self.headers = Rack::Headers.new.merge(headers)
self.headers = HTTPI::Utils::Headers.new.merge(headers)
self.raw_body = body
end

Expand Down
238 changes: 238 additions & 0 deletions lib/httpi/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
# mostly verbatim from: https://github.com/rack/rack/blob/main/lib/rack/headers.rb
# Because this is part of httpi's public API, its better not to load an external
# library for it.
module HTTPI
module Utils
# HTTPI::Utils::Headers is a Hash subclass that downcases all keys.
class Headers < Hash
KNOWN_HEADERS = {}
%w(
Accept-CH
Accept-Patch
Accept-Ranges
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Max-Age
Age
Allow
Alt-Svc
Cache-Control
Connection
Content-Disposition
Content-Encoding
Content-Language
Content-Length
Content-Location
Content-MD5
Content-Range
Content-Security-Policy
Content-Security-Policy-Report-Only
Content-Type
Date
Delta-Base
ETag
Expect-CT
Expires
Feature-Policy
IM
Last-Modified
Link
Location
NEL
P3P
Permissions-Policy
Pragma
Preference-Applied
Proxy-Authenticate
Public-Key-Pins
Referrer-Policy
Refresh
Report-To
Retry-After
Server
Set-Cookie
Status
Strict-Transport-Security
Timing-Allow-Origin
Tk
Trailer
Transfer-Encoding
Upgrade
Vary
Via
WWW-Authenticate
Warning
X-Cascade
X-Content-Duration
X-Content-Security-Policy
X-Content-Type-Options
X-Correlation-ID
X-Correlation-Id
X-Download-Options
X-Frame-Options
X-Permitted-Cross-Domain-Policies
X-Powered-By
X-Redirect-By
X-Request-ID
X-Request-Id
X-Runtime
X-UA-Compatible
X-WebKit-CS
X-XSS-Protection
).each do |str|
downcased = str.downcase.freeze
KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
end

def self.[](*items)
if items.length % 2 != 0
if items.length == 1 && items.first.is_a?(Hash)
new.merge!(items.first)
else
raise ArgumentError, "odd number of arguments for Utils::Headers"
end
else
hash = new
loop do
break if items.length == 0
key = items.shift
value = items.shift
hash[key] = value
end
hash
end
end

def [](key)
super(downcase_key(key))
end

def []=(key, value)
super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
end
alias store []=

def assoc(key)
super(downcase_key(key))
end

def compare_by_identity
raise TypeError, "Utils::Headers cannot compare by identity, use regular Hash"
end

def delete(key)
super(downcase_key(key))
end

def dig(key, *a)
super(downcase_key(key), *a)
end

def fetch(key, *default, &block)
key = downcase_key(key)
super
end

def fetch_values(*a)
super(*a.map!{|key| downcase_key(key)})
end

def has_key?(key)
super(downcase_key(key))
end
alias include? has_key?
alias key? has_key?
alias member? has_key?

def invert
hash = self.class.new
each{|key, value| hash[value] = key}
hash
end

def merge(hash, &block)
dup.merge!(hash, &block)
end

def reject(&block)
hash = dup
hash.reject!(&block)
hash
end

def replace(hash)
clear
update(hash)
end

def select(&block)
hash = dup
hash.select!(&block)
hash
end

def to_proc
lambda{|x| self[x]}
end

def transform_values(&block)
dup.transform_values!(&block)
end

def update(hash, &block)
hash.each do |key, value|
self[key] = if block_given? && include?(key)
block.call(key, self[key], value)
else
value
end
end
self
end
alias merge! update

def values_at(*keys)
keys.map{|key| self[key]}
end

# :nocov:
if RUBY_VERSION >= '2.5'
# :nocov:
def slice(*a)
h = self.class.new
a.each{|k| h[k] = self[k] if has_key?(k)}
h
end

def transform_keys(&block)
dup.transform_keys!(&block)
end

def transform_keys!
hash = self.class.new
each do |k, v|
hash[yield k] = v
end
replace(hash)
end
end

# :nocov:
if RUBY_VERSION >= '3.0'
# :nocov:
def except(*a)
super(*a.map!{|key| downcase_key(key)})
end
end

private

def downcase_key(key)
key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
end
end
end
end
2 changes: 1 addition & 1 deletion spec/httpi/request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
describe "#headers" do
it "lets you specify a Hash of HTTP request headers" do
request.headers = { "Accept-Encoding" => "gzip" }
expect(request.headers).to eq Rack::Headers.new.merge({ "Accept-Encoding" => "gzip" })
expect(request.headers).to eq HTTPI::Utils::Headers.new.merge({ "Accept-Encoding" => "gzip" })
end

it "defaults to return an empty Hash" do
Expand Down
4 changes: 2 additions & 2 deletions spec/httpi/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

describe "#headers" do
it "returns the HTTP response headers" do
expect(response.headers).to eq Rack::Headers.new.merge({ "Content-Encoding" => "gzip" })
expect(response.headers).to eq HTTPI::Utils::Headers.new.merge({ "Content-Encoding" => "gzip" })
end
end

Expand Down Expand Up @@ -116,7 +116,7 @@

describe "#headers" do
it "returns the HTTP response headers" do
expect(response.headers).to eq Rack::Headers.new.merge({ "Content-Type" => "application/dime" })
expect(response.headers).to eq HTTPI::Utils::Headers.new.merge({ "Content-Type" => "application/dime" })
end
end

Expand Down

0 comments on commit ec5fd17

Please sign in to comment.