The Case of the Broken API Params

I’ve been working on a Ruby program and had received a new version of an API to interface with. The new version had moved from GET requests to POST requests, so where before I was getting information with:
 def get_events(start_date: Date.today, end_date: Date.today)
    uri = URI(@session.base_url + '/events' + '?token=' + @session.get_token +
                  '&site_id=' + @session.site_id +
                  '&nview=y' +
                  '&start_date=' + api_formatted_date(start_date) +
                  '&end_date=' + api_formatted_date(end_date))
    @response[:events] = Net::HTTP.get(uri)
  end
Now I had to use Net:HTTP.post:
  def get_events(page: 1)
    uri = URI(@session.base_url + '/events' + '?site_id=' + @session.site_id)
    req = Net::HTTP::Post.new(@access_url)
    req['x-api-key'] = @session.gateway_key
    req['Authorization'] = @session.token
    req['page'] = page
    @response = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
    @response[:events] = Net::HTTP.get(uri)
  end
Strangely, this wasn’t working. I kept getting back errors from the API saying ‘Invalid Request: parameter ‘x-api-key’ missing. It took way too long, but I finally came across this StackOverflow article. In essence, RFC 2616 states that param names should be case insensitive. First I tried changing my ‘Authorization’ parameter, but no, I kept getting the same error. The API documentation had everything cased exactly as I had in my code above, so what was up? It turns out, the Ruby Net library was the culprit. Net::HttpHeader downcases all the params:
               # File net/http/header.rb, line 67
def add_field(key, val)
  stringified_downcased_key = key.downcase.to_s
  if @header.key?(stringified_downcased_key)
    append_field_value(@header[stringified_downcased_key], val)
  else
    set_field(key, val)
  end
end
…and Net::HTTPGenericRequest then capitalizes them:
  def write_header(sock, ver, path)
    reqline = "#{@method} #{path} HTTP/#{ver}"
    if /[\r\n]/ =~ reqline
      raise ArgumentError, "A Request-Line must not contain CR or LF"
    end
    buf = ""
    buf << reqline << "\r\n"
    each_capitalized do |k,v|
      buf << "#{k}: #{v}\r\n"
    end
    buf << "\r\n"
    sock.write buf
  end
I’m guessing this is for readability, and per the RFC it shouldn’t matter, but I can’t tell the API receiving my request that. So, as the Stack Overflow article suggested, I created a separate CasesSensitiveString model:
class CaseSensitiveString < String
  def downcase
    self
  end

  def capitalize
    self
  end

  def to_s
    self
  end
end
…and then inserted that into my code:
  def get_events(page: 1)
    uri = URI.parse(@session.base_url + '/events' + '?site_id=' + @session.site_id.to_s)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = (uri.scheme == 'https')
    req = Net::HTTP::Post.new(uri.request_uri)
    req[CaseSensitiveString.new('x-api-key')] = @session.gateway_key
    req[CaseSensitiveString.new('Authorization')] = @session.token
    req[CaseSensitiveString.new('page')] = page
    request_response = http.request(req)
    @response[:events] = JSON.parse request_response.body, object_class: OpenStruct
  end
Voila, the vendor’s API now worked!