Custom Http Header and Ruby Standard Library

3 minute read

The problem

One day at work I got an escalation from one of the third-party vendors that all the api calls to them were silently getting rejected on their end. They provided the explanation that one of the HTTP header that was used to supplying the api key itself, (let’s call it API-KEY) was being sent incorrectly.

They wanted the header to be a lower case like api-key but we started sending it in upper case API-KEY. This should not happen since we had a patch in place, to handle this situation.

Little background about HTTP headers and NET::HTTP

As per the RFC, http headers are case-insensitive, which means the destination application should be able to understand both the uppercase and lowercase.

Each header field consists of a name followed by a colon (“:”) and the field value. Field names are case-insensitive

Since for most of the applications, http header is case-insensitive. Ruby’s standard networking library NET::HTTP converts every header to uppercase which is not an issue for most of the third party apis.

Many popular libraries like Httparty use Net::HTTP as backend.

But sometimes we need to send a particular http header as is, without any modifications.

To prevent http header keys from being modified by NET::HTTP, I used this solution from

which worked fine.

class ImmutableKey < String 
         def capitalize 

and using the above key as follows

{"api-key"): 'SECRET-KEY' } 

As per the third party, this started occurring from a particular time and then it occurred to me, the timeline matches perfectly with our rails upgrade from rails 4 to rails 5. To verify the hunch, I ran the same code again in both rails 4 and rails 5 boxes with HTTParty debug log on and yep, there it was. Headers were being capitalized in new rails boxes.

# Rails 4 box
opening connection to
starting SSL for
SSL established

<- POST "/endpoint HTTP/1.1\r\napi-key: 'SECRET'
# Rails 5 box
opening connection to
starting SSL for
SSL established

<- POST "/endpoint HTTP/1.1\r\nAPI-KEY: 'SECRET'

But why did it happen, my first thought was to check for gem version of httparty and even though there was a bump from .0.14 to 0.17, further debugging proved that httparty was not the issue and mutation of forms were happening at Net::HTTP level.

def setup_raw_request
          @raw_request =
          @raw_request.body_stream = options[:body_stream] if options[:body_stream]
          if options[:headers].respond_to?(:to_hash)
            headers_hash = options[:headers].to_hash
            # If the caller specified a header of 'Accept-Encoding', assume they want to
            # deal with encoding of content. Disable the internal logic in Net:HTTP
            # that handles encoding, if the platform supports it.
            if @raw_request.respond_to?(:decode_content) && (headers_hash.key?('Accept-Encoding') || headers_hash.key?('accept-encoding'))
              # Using the '[]=' sets decode_content to false
              @raw_request['accept-encoding'] = @raw_request['accept-encoding']

specifically this line


So if NET::HTTP was the issue here then why did rails upgrade break it ?

It was due to ruby version upgrade, earlier we were using ruby 2.1.3 and with the rails upgrade we jumped to ruby 2.5.2 which means standard library also had some changes.

So let see the diff between two versions

Older ruby version 2.1.3 had

def each_capitalized
    block_given? or return enum_for(__method__)
    @header.each do |k,v|
      yield capitalize(k), v.join(', ')

  alias canonical_each each_capitalized

  def capitalize(name)
    name.split(/-/).map {|s| s.capitalize }.join('-')

while ruby 2.5 introduced this commit introduced some changes to underlying captialize method by using to_s

    name.to_s.split(/-/).map {|s| s.capitalize }.join('-')

which caused our ImmutableString class to return a new string object instead an object of ImmutableString class with capitalized frozen

ImmutableKey("new").class # ImmutableKey
ImmutableKey("new").to_s.class # String
ImmutableKey("new").to_str.class # String


Fix was to make the to_s and to_str return the self so that the returned object is an instance of ImmutableKey instead of the base string class

class ImmutableKey < String 
         def capitalize 
         def to_s
         alias_method :to_str, :to_s

Debugging the issue was fun though :D

If I missed out on something or something is not correct, then do let me know and I will correct it :)