From 75fd1830865bdb3a8f49d83ba4157421d2ec74e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=20Guimar=C3=A3es?= Date: Tue, 15 Sep 2026 18:37:12 -0300 Subject: [PATCH] fix: Invalidate the request URL even when the response has no headers A response cut short by a timeout or a connection reset can reach the delete path with nil headers, and reading Location out of it raised NoMethodError inside on_complete. Fall back to an empty header set so the request URL is still invalidated. Fixes #142 --- lib/faraday/http_cache.rb | 8 +++++--- spec/http_cache_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/faraday/http_cache.rb b/lib/faraday/http_cache.rb index 3e4f070..ffa7448 100644 --- a/lib/faraday/http_cache.rb +++ b/lib/faraday/http_cache.rb @@ -314,9 +314,11 @@ def authorization_bearing? end def delete(request, response) - headers = %w[Location Content-Location] - headers.each do |header| - url = response.headers[header] + # A response cut short by a timeout or a reset can arrive without + # headers; there is still an entry to invalidate for the request URL. + headers = response.headers || {} + %w[Location Content-Location].each do |header| + url = headers[header] @strategy.delete(url) if url end diff --git a/spec/http_cache_spec.rb b/spec/http_cache_spec.rb index 1a12270..6e69218 100644 --- a/spec/http_cache_spec.rb +++ b/spec/http_cache_spec.rb @@ -96,6 +96,33 @@ client.get('broken') end + it 'still expires the request URL when the response has no headers' do + store = Faraday::HttpCache::MemoryStore.new + cached = Faraday.new(url: ENV['FARADAY_SERVER']) do |stack| + stack.use Faraday::HttpCache, store: store + stack.adapter ENV['FARADAY_ADAPTER'].to_sym + end + # Mimics an adapter that gives up mid-response: the env is completed + # with no status worth the name and no response headers at all. + headerless_adapter = Class.new(Faraday::Adapter) do + def call(env) + super + env.status = 0 + env.response_headers = nil + env.response.finish(env) + end + end + broken = Faraday.new(url: ENV['FARADAY_SERVER']) do |stack| + stack.use Faraday::HttpCache, store: store + stack.adapter headerless_adapter + end + + cached.get('get') + broken.post('get') + + expect(cached.get('get').body).to eq('2') + end + it 'expires entries for the "Location" header' do client.get('get') client.post('delete-with-location')