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!