ruby / net-http

Net::HTTP provides a rich library which can be used to build HTTP user-agents.
Other
101 stars 66 forks source link

Issue with Content Type #148

Open OskarEichler opened 1 year ago

OskarEichler commented 1 year ago

We've identified an issue in the current release of net-http (0.3.2).

When setting a 'Content-Type' header on a POST request like so: request['Content-Type'] = 'application/json'

It does set the header, however, it does not recognize that the Content-Type is set correctly and adds an additional header to the request with the default application/x-www-form-urlencoded

This can be seen when calling: request.to_hash.inspect:

{"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], "Accept"=>["*/*"], "User-Agent"=>["Ruby"], "Host"=>["api.songstats.com"], "Content-Type"=>["application/json"], "connection"=>["close"], "host"=>["api.songstats.com"], "content-length"=>["290"], "content-type"=>["application/x-www-form-urlencoded"]}

The only way to force the application/json header to go through is to explicitly set it in the request:

request.content_type = 'application/json'

So actually it needs to be set twice in order to fully work:

request['Content-Type'] = 'application/json'
request.content_type = 'application/json'

Also when setting request['content-type'] = 'application/json' in lower case it's throwing the error:

NoMethodError: undefined method `split' for nil:NilClass
from /Users/Oskar/.rbenv/versions/3.2.1/lib/ruby/3.2.0/net/http/header.rb:713:in `main_type'

It would be great if this can be streamlined so that setting the content-type in the headers immediately propagates across the entire request, without the need to set it multiple times or be cautious of case sensitivity. This took us a couple of hours to debug because we were un-aware that it currently sends the same header twice in the same request with different values.

0x1eef commented 1 year ago

@OskarEichler Can you provide an isolated reproduction case ? Are you using set_form, set_form_data ? I tried to reproduce, but no luck.

OskarEichler commented 1 year ago

@0x1eef Thanks for looking into this - here is how we send the request:

      https = Net::HTTP.new(url.host, url.port)
      https.use_ssl = true

      request_method = method == :get ? CaseSensitiveGet : CaseSensitivePost

      request = request_method.new(url, {myCasedHeader: 'x-api-key'})
      request["Authorization"] = "Bearer #{access_token}"
      request["x-api-key"] = AMAZON_API_KEY
      request['Content-Type'] = 'application/json'
      request.content_type = 'application/json'
      request.body = payload.to_json

      data = https.request(request)

Also note that we had to create custom methods CaseSensitiveGet and CaseSensitivePost as you library for some reason Capitalized the header keys. In the case of amazon they do not recognize the key 'X-api-key', only 'x-api-key', so we had to manually create the modifications so that they don't get auto cased. It would be great if you can fix that so that the keys are passed exactly as they are being provided by the user.

Here is our CaseSensitiveGet class for context:

class CaseSensitiveGet < Net::HTTP::Get
  def initialize_http_header(headers)
    @header = {}
    headers.each{|k,v| @header[k.to_s] = [v] }
  end

  def [](name)
    @header[name.to_s]
  end

  def []=(name, val)
    if val
      @header[name.to_s] = [val]
    else
      @header.delete(name.to_s)
    end
  end

  def capitalize(name)
    name
  end
end