{"record":{"id":"539fd5b377c22ff6","repo":"puppetlabs/puppet","slug":"put-requires-a-content-type-header","errorCode":null,"errorMessage":"'put' requires a 'content-type' header","messagePattern":"'put' requires a 'content-type' header","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/client.rb","lineNumber":247,"sourceCode":"  # @param [Hash] headers merged with the default headers defined by the client. The\n  #   `Content-Type` header is required and should correspond to the type of data passed\n  #   as the `body` argument.\n  # @param [Hash] params encoded and set as the url query\n  # @!macro request_options\n  #\n  # @return [Puppet::HTTP::Response] the response\n  #\n  # @api public\n  def put(url, body, headers: {}, params: {}, options: {})\n    raise ArgumentError, \"'put' requires a string 'body' argument\" unless body.is_a?(String)\n\n    url = encode_query(url, params)\n\n    request = Net::HTTP::Put.new(url, @default_headers.merge(headers))\n    request.body = body\n    request.content_length = body.bytesize\n\n    raise ArgumentError, \"'put' requires a 'content-type' header\" unless request['Content-Type']\n\n    execute_streaming(request, options: options)\n  end\n\n  # Submits a POST HTTP request to the given url\n  #\n  # @param [URI] url the location to submit the http request\n  # @param [String] body the body of the POST request\n  # @param [Hash] headers merged with the default headers defined by the client. The\n  #   `Content-Type` header is required and should correspond to the type of data passed\n  #   as the `body` argument.\n  # @param [Hash] params encoded and set as the url query\n  # @!macro request_options\n  #\n  # @yield [Puppet::HTTP::Response] if a block is given yields the response\n  #\n  # @return [Puppet::HTTP::Response] the response\n  #","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/client.rb#L229-L265","documentation":"Puppet::HTTP::Client#put requires a Content-Type header on every request: the client's default headers do not include one, so if the caller's headers hash doesn't supply it (with that exact case), an ArgumentError is raised after the body is set but before the request is sent.","triggerScenarios":"client.put(url, 'text') with headers: {} ; passing the header lowercase as 'content-type' — Net::HTTP header lookup in the guard is case-sensitive, so request['Content-Type'] stays nil even though Net::HTTP would send it.","commonSituations":"Sending JSON without 'application/json' because the old HttpPool API added it; header-name casing copied from a different HTTP library; proxy code forwarding only some headers.","solutions":["Add the header with exact casing: headers: { 'Content-Type' => 'application/json' }.","Match the type to the body's serialization (application/json for JSON, text/plain for plain text, application/x-www-form-urlencoded for form bodies).","Prefer the high-level Puppet::HTTP::Service APIs, which set Content-Type for you."],"exampleFix":"# before\nclient.put(url, JSON.generate(data), headers: {})\n\n# after\nclient.put(url, JSON.generate(data), headers: { 'Content-Type' => 'application/json' })","handlingStrategy":"validation","validationCode":"headers = { 'Content-Type' => 'application/json' }.merge(headers)\nclient.put(url, body, headers: headers)","typeGuard":"has_content_type = ->(h) { h.any? { |k, _| k.is_a?(String) && k == 'Content-Type' } }\nraise ArgumentError, 'Content-Type required' unless has_content_type.call(headers)","tryCatchPattern":null,"preventionTips":["Build headers through one helper that always sets Content-Type with exact casing.","Match Content-Type to how the body was serialized."],"tags":["puppet","http","put","headers","content-type"],"backgroundTag":"missing-content-type-header","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}