{"record":{"id":"e912a31049ad634c","repo":"puppetlabs/puppet","slug":"no-content-type-in-http-response-cannot-parse","errorCode":null,"errorMessage":"No content type in http response; cannot parse","messagePattern":"No content type in http response; cannot parse","errorType":"exception","errorClass":"Puppet::HTTP::ProtocolError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/service.rb","lineNumber":132,"sourceCode":"      end\n    end\n    modified_headers\n  end\n\n  def build_url(api, server, port)\n    URI::HTTPS.build(host: server,\n                     port: port,\n                     path: api).freeze\n  end\n\n  def get_mime_types(model)\n    network_formats = model.supported_formats - EXCLUDED_FORMATS\n    network_formats.map { |f| model.get_format(f).mime }\n  end\n\n  def formatter_for_response(response)\n    header = response['Content-Type']\n    raise Puppet::HTTP::ProtocolError, _(\"No content type in http response; cannot parse\") unless header\n\n    header.gsub!(/\\s*;.*$/, '') # strip any charset\n\n    formatter = Puppet::Network::FormatHandler.mime(header)\n    raise Puppet::HTTP::ProtocolError, \"Content-Type is unsupported\" if EXCLUDED_FORMATS.include?(formatter.name)\n\n    formatter\n  end\n\n  def serialize(formatter, object)\n    formatter.render(object)\n  rescue => err\n    raise Puppet::HTTP::SerializationError.new(\"Failed to serialize #{object.class} to #{formatter.name}: #{err.message}\", err)\n  end\n\n  def serialize_multiple(formatter, object)\n    formatter.render_multiple(object)\n  rescue => err","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/service.rb#L114-L150","documentation":"All Puppet::HTTP::Service subclasses (CA, fileserver, compiler, report) pick a deserializer for a response from its Content-Type header via formatter_for_response. A 2xx response with no Content-Type cannot be mapped to any format, so Puppet::HTTP::ProtocolError is raised before body parsing.","triggerScenarios":"The server (or a proxy in front of it) replies 200 with a body but no Content-Type; a middleware/gateway stripping the header; hitting a plain HTTP port or health-check handler with a puppet path so the reply lacks puppet headers.","commonSituations":"Custom route handlers on puppetserver that omit content_type; misconfigured nginx/ATS proxying that drops the header; DNS/port mistakes landing on an unrelated web service that answers 200 bare.","solutions":["Reproduce with curl -i <url> and confirm the missing header at the client's vantage point.","Fix the origin/proxy to send the correct type (application/json, application/vnd.puppet.pson+json, etc.).","Verify the URL actually targets a puppet route (e.g. /puppet/v3/...) and the right port (8140).","For custom puppetserver endpoints, set response content type explicitly in the Ruby handler."],"exampleFix":"# custom puppetserver route - before\nget '/custom/status' do |req, res|\n  res.body = '{}'\nend\n\n# after\nget '/custom/status' do |req, res|\n  res['Content-Type'] = 'application/json'\n  res.body = '{}'\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  service.get_cert(name)\nrescue Puppet::HTTP::ProtocolError => e\n  raise unless e.message.include?('No content type')\n  # dump headers for diagnosis; likely wrong route or header-stripping proxy\n  raise\nend","preventionTips":["curl -i the endpoint from the client host to verify Content-Type end-to-end.","Custom puppetserver routes must set Content-Type explicitly.","Point clients at puppet routes on port 8140, not auxiliary web services."],"tags":["puppet","http","content-type","response","protocol"],"backgroundTag":"missing-content-type-header","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}