Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I preserve case with http.get?

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?

like image 719
Yaron Naveh Avatar asked Jan 14 '12 19:01

Yaron Naveh


4 Answers

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")
like image 170
yfeldblum Avatar answered Oct 09 '22 11:10

yfeldblum


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
# >> 
like image 33
Joshua Cheek Avatar answered Oct 09 '22 12:10

Joshua Cheek


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.

like image 40
the Tin Man Avatar answered Oct 09 '22 13:10

the Tin Man


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
like image 31
Vladimir Eltchinov Avatar answered Oct 09 '22 12:10

Vladimir Eltchinov