Skip to content

NoMethodError: undefined method '[]' for nil in HttpCache#delete when response has nil headers #142

@aminbenselim

Description

@aminbenselim

Summary

When an upstream service returns a broken/incomplete response (e.g. due to a timeout or network interruption), response.headers can be nil. The delete method then raises NoMethodError: undefined method '[]' for nil inside an on_complete callback, which bubbles up to the caller instead of being handled gracefully.

Steps to Reproduce

  1. Use faraday-http-cache with any unsafe HTTP method (POST, PUT, PATCH, DELETE)
  2. Have the upstream service fail in a way that produces a response with nil headers (e.g. timeout via net-http-persistent adapter, TCP reset, or connection interruption)
  3. Observe the error raised from on_complete

Backtrace

NoMethodError: undefined method '[]' for nil
    url = response.headers[header]
                          ^^^^^^^^
faraday-http-cache-2.7.0/lib/faraday/http_cache.rb:284:in `block in Faraday::HttpCache#delete'
faraday-http-cache-2.7.0/lib/faraday/http_cache.rb:283:in `Array#each'
faraday-http-cache-2.7.0/lib/faraday/http_cache.rb:283:in `Faraday::HttpCache#delete'
faraday-http-cache-2.7.0/lib/faraday/http_cache.rb:157:in `block in Faraday::HttpCache#call!'
faraday-2.14.2/lib/faraday/response.rb:46:in `Faraday::Response#on_complete'
faraday-http-cache-2.7.0/lib/faraday/http_cache.rb:156:in `Faraday::HttpCache#call!'

Root Cause

In HttpCache#delete, the code iterates over Location and Content-Location headers to invalidate cached URLs:

def delete(request, response)
  headers = %w[Location Content-Location]
  headers.each do |header|
    url = response.headers[header]  # raises if response.headers is nil
    @strategy.delete(url) if url
  end
  @strategy.delete(request.url)
end

should_delete? returns true for any unsafe method with a non-4xx status — which includes broken responses with nil headers that result in a status of 0 or similar edge values from the adapter.

Suggested Fix

Use safe navigation to guard against nil headers:

def delete(request, response)
  headers = %w[Location Content-Location]
  headers.each do |header|
    url = response.headers&.[](header)
    @strategy.delete(url) if url
  end
  @strategy.delete(request.url)
end

Environment

  • faraday-http-cache 2.7.0 (reproduced from 2.5.1 onward, likely present since 1.0.0)
  • faraday 2.14.2
  • faraday adapter: net-http-persistent
  • Ruby 3.4
  • Triggered by POST requests to a slow/degraded upstream service with a short timeout (150ms)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions