jnunemaker/httparty · error · HTTParty::DuplicateLocationHeader
#{response}
Error message
#{response} What it means
HTTParty raises HTTParty::DuplicateLocationHeader when it tries to follow a redirect and the response contains more than one Location header (last_response.get_fields('location') returns an array with count > 1). Duplicate Location headers make the redirect target ambiguous and are a classic sign of response splitting or a broken proxy, so HTTParty refuses to guess which one to follow. The exception inherits from HTTParty::ResponseError, so e.response holds the Net::HTTPResponse that triggered it; the message is just the response object rendered as a string, which is why it looks like "#{response}".
Source
Thrown at lib/httparty/request.rb:366
end
if http_method == Net::HTTP::Get
clear_body
end
capture_cookies(last_response)
perform(&block)
end
def handle_host_redirection
check_duplicate_location_header
redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
return if redirect_path.relative? || path.host == redirect_path.host || uri.host == redirect_path.host
@changed_hosts = true
end
def check_duplicate_location_header
location = last_response.get_fields('location')
if location.is_a?(Array) && location.count > 1
raise DuplicateLocationHeader.new(last_response)
end
end
def send_authorization_header?
!@changed_hosts
end
def response_redirects?
case last_response
when Net::HTTPNotModified # 304
false
when Net::HTTPRedirection
options[:follow_redirects] && last_response.key?('location')
end
end
def parse_response(body)
parser.call(body, format)View on GitHub (pinned to 8f4a09e343)
Solutions
- Inspect e.response.headers.get_fields('location') in the rescue block to confirm the duplicate and see both values.
- Fix the origin: ensure only one Location header is emitted on redirect responses (check proxy add_header/append and backend framework redirect helpers).
- If the error comes from a test, fix the stub: WebMock `to_redirect('http://x/')` instead of hand-writing headers with multiple Location entries.
- As a last resort, rescue HTTParty::DuplicateLocationHeader and treat it as a protocol error (retry without redirects via follow_redirects(false) and inspect manually).
Example fix
# before (server/stub sends two Location headers)
stub_request(:get, 'http://api.example.com/old')
.to_return(status: 302, headers: { 'Location' => ['/a', '/b'] })
Foo.get('http://api.example.com/old')
# => HTTParty::DuplicateLocationHeader
# after (exactly one Location header)
stub_request(:get, 'http://api.example.com/old')
.to_return(status: 302, headers: { 'Location' => '/a' })
Foo.get('http://api.example.com/old') Defensive patterns
Strategy: try-catch
Try / catch
begin
Foo.get('http://api.example.com/old')
rescue HTTParty::DuplicateLocationHeader => e
locations = e.response.headers.get_fields('location')
Rails.logger.error("ambiguous redirect, #{locations.size} Location headers: #{locations.inspect}")
raise
end Prevention
- Stub exactly one Location header in WebMock/VCR redirect stubs.
- Audit proxies/LBs (nginx add_header, HAPCRoxy http-response) so they replace, not append, Location on rewrites.
- Treat DuplicateLocationHeader as a protocol/security signal in monitoring, not as a retryable error.
When it happens
Trigger: Any request with follow_redirects enabled (the default) where the server answers 301/302/303/307/308 with two or more Location headers. check_duplicate_location_header runs inside handle_host_redirection before the redirect is parsed, e.g. `Foo.get('http://api.example.com/old')` against a host whose 302 response repeats Location twice.
Common situations: WebMock/VCR stubs that declare the Location header twice (once as a scalar and once in a with-headers hash), misconfigured nginx/HAProxy setups where both the backend and the proxy add Location, CDN edge rewrites that stack on top of origin redirects, and actual HTTP response-splitting from a vulnerable backend.
Related errors
- Headers must be an object which responds to #to_hash
- :headers must be a hash
- Requested URI '#{new_uri}' has host '#{new_uri.host}' but th
- Default params must be an object which responds to #to_hash
- Cookies must be an object which responds to #to_hash
AI-assisted analysis of jnunemaker/httparty@8f4a09e343 (2026-08-21).
Data as JSON: /api/errors/24dc9cdcb3979f36.
Report an issue: GitHub.