I have a requirement to send an HTTP header in a specific character-case. I am aware that this is against the RFC, but I have a requirement.
http.get
seems to change the case of the headers dictionary I supply it. How can I preserve the character-case?
Based on the Tin Man's answer that the Net::HTTP
library is calling #downcase
on your custom header key (and all header keys), here are some additional options that don't monkey-patch the whole of Net::HTTP
.
You could try this:
custom_header_key = "X-miXEd-cASe"
def custom_header_key.downcase
self
end
To avoid clearing the method cache, either store the result of the above in a class-level constant:
custom_header_key = "X-miXEd-cASe"
def custom_header_key.downcase
self
end
CUSTOM_HEADER_KEY = custom_header_key
or subclass String to override that particular behavior:
class StringWithIdentityDowncase < String
def downcase
self
end
end
custom_header_key = StringWithIdentityDowncase.new("X-miXEd-cASe")
The accepted answer does not work. Frankly, I doubt that it ever did since it looks like it would have had to also override split and capitalize, I followed that method back a few commits, it's been that way at least since 2004.
Here is my solution, in answer to this closed question:
require 'net/http'
class Net::HTTP::ImmutableHeaderKey
attr_reader :key
def initialize(key)
@key = key
end
def downcase
self
end
def capitalize
self
end
def split(*)
[self]
end
def hash
key.hash
end
def eql?(other)
key.eql? other.key.eql?
end
def to_s
key
end
end
Now you need to be sure to always use instances of this class as your keys.
request = Net::HTTP::Get.new('/')
user_key = Net::HTTP::ImmutableHeaderKey.new("user")
request[user_key] = "James"
require 'stringio'
StringIO.new.tap do |output|
request.exec output, 'ver', 'path'
puts output.string
end
# >> GET path HTTP/ver
# >> Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
# >> Accept: */*
# >> User-Agent: Ruby
# >> user: James
# >>
Mine is one way to do it, but I recommend doing it as @yfeldblum recommends, simply short-circuit downcase
for the header keys that need to have their case left-alone.
In multiple places in Net::HTTP::HTTPHeader the headers get folded to lower-case using downcase
.
I think it is pretty drastic to change that behavior, but this will do it. Add this to your source and it will redefine the methods in the HTTPHeader module that had downcase
in them.
module HTTPHeader
def initialize_http_header(initheader)
@header = {}
return unless initheader
initheader.each do |key, value|
warn "net/http: warning: duplicated HTTP header: #{key}" if key?(key) and $VERBOSE
@header[key] = [value.strip]
end
end
def [](key)
a = @header[key] or return nil
a.join(', ')
end
def []=(key, val)
unless val
@header.delete key
return val
end
@header[key] = [val]
end
def add_field(key, val)
if @header.key?(key)
@header[key].push val
else
@header[key] = [val]
end
end
def get_fields(key)
return nil unless @header[key]
@header[key].dup
end
def fetch(key, *args, &block) #:yield: +key+
a = @header.fetch(key, *args, &block)
a.kind_of?(Array) ? a.join(', ') : a
end
# Removes a header field.
def delete(key)
@header.delete(key)
end
# true if +key+ header exists.
def key?(key)
@header.key?(key)
end
def tokens(vals)
return [] unless vals
vals.map {|v| v.split(',') }.flatten\
.reject {|str| str.strip.empty? }\
.map {|tok| tok.strip }
end
end
I think this is a brute force way of going about it, but nothing else more elegant jumped to mind.
While this should fix the problem for any Ruby libraries using Net::HTTP, it will probably fail for any gems that use Curl or libcurl.
Joshua Cheek's answer is great, but it does in work anymore in Ruby 2.3
This modification fix it:
class Net::HTTP::ImmutableHeaderKey
...
def to_s
caller.first.match(/capitalize/) ? self : @key
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With