Skip to content

Commit 9ccf332

Browse files
committed
support for range and content nego headers
1 parent acb6135 commit 9ccf332

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

src/Symfony/Component/HttpClient/CachingHttpClient.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,10 @@
3434
* Adds caching on top of an HTTP client (per RFC 9111).
3535
*
3636
* Known omissions / partially supported features per RFC 9111:
37-
* 1. Range requests:
38-
* - All range requests ("partial content") are passed through and never cached.
39-
* 2. stale-while-revalidate:
37+
* 1. stale-while-revalidate:
4038
* - There's no actual "background revalidation" for stale responses, they will
4139
* always be revalidated.
42-
* 3. min-fresh, max-stale, only-if-cached:
40+
* 2. min-fresh, max-stale, only-if-cached:
4341
* - Request directives are not parsed; the client ignores them.
4442
*
4543
* @see https://www.rfc-editor.org/rfc/rfc9111
@@ -68,6 +66,21 @@ class CachingHttpClient implements HttpClientInterface, ResetInterface
6866
* The HTTP methods that will trigger a cache invalidation.
6967
*/
7068
private const UNSAFE_METHODS = ['POST', 'PUT', 'DELETE', 'PATCH'];
69+
/**
70+
* Headers that influence the response and may affect caching behavior.
71+
*/
72+
private const RESPONSE_INFLUENCING_HEADERS = [
73+
'accept' => true,
74+
'accept-charset' => true,
75+
'accept-encoding' => true,
76+
'accept-language' => true,
77+
'authorization' => true,
78+
'cookie' => true,
79+
'expect' => true,
80+
'host' => true,
81+
'range' => true,
82+
'user-agent' => true,
83+
];
7184
/**
7285
* Headers that MUST NOT be stored as per RFC 9111 Section 3.1.
7386
*/
@@ -144,16 +157,24 @@ public function request(string $method, string $url, array $options = []): Respo
144157
$this->cache->invalidateTags([$fullUrlTag]);
145158
}
146159

147-
if ('' !== $options['body'] || isset($options['normalized_headers']['range']) || !\in_array($method, self::CACHEABLE_METHODS, true)) {
160+
if ('' !== $options['body'] || !\in_array($method, self::CACHEABLE_METHODS, true)) {
148161
return new AsyncResponse($this->client, $method, $url, $options);
149162
}
150163

151-
$requestHash = self::hash($method.$fullUrl);
164+
$requestHash = self::hash($method.$fullUrl.json_encode(array_intersect_key($options['normalized_headers'], self::RESPONSE_INFLUENCING_HEADERS), \JSON_THROW_ON_ERROR));
152165
$varyKey = "vary_{$requestHash}";
153-
$varyFields = $this->cache->get($varyKey, static fn (): array => []);
166+
$varyFields = $this->cache->get($varyKey, static function (ItemInterface $item, bool &$save): array {
167+
$save = false;
168+
169+
return [];
170+
});
154171

155172
$metadataKey = self::getMetadataKey($requestHash, $options['normalized_headers'], $varyFields);
156-
$cachedData = $this->cache->get($metadataKey, static fn (): null => null);
173+
$cachedData = $this->cache->get($metadataKey, static function (ItemInterface $item, bool &$save): null {
174+
$save = false;
175+
176+
return null;
177+
});
157178

158179
$freshness = null;
159180
if (\is_array($cachedData)) {

src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,4 +870,29 @@ public function testHeuristicFreshnessWithLastModified()
870870
self::assertSame(200, $response->getStatusCode());
871871
self::assertSame('bar', $response->getContent());
872872
}
873+
874+
public function testResponseInfluencingHeadersAffectCacheKey()
875+
{
876+
$mockClient = new MockHttpClient([
877+
new MockResponse('response for en', ['http_code' => 200]),
878+
new MockResponse('response for fr', ['http_code' => 200]),
879+
]);
880+
881+
$client = new CachingHttpClient($mockClient, $this->cacheAdapter);
882+
883+
// First request with Accept-Language: en
884+
$response = $client->request('GET', 'http://example.com/lang-test', ['headers' => ['Accept-Language' => 'en']]);
885+
self::assertSame(200, $response->getStatusCode());
886+
self::assertSame('response for en', $response->getContent());
887+
888+
// Same request with Accept-Language: en should return cached response
889+
$response = $client->request('GET', 'http://example.com/lang-test', ['headers' => ['Accept-Language' => 'en']]);
890+
self::assertSame(200, $response->getStatusCode());
891+
self::assertSame('response for en', $response->getContent());
892+
893+
// Request with Accept-Language: fr should fetch new response
894+
$response = $client->request('GET', 'http://example.com/lang-test', ['headers' => ['Accept-Language' => 'fr']]);
895+
self::assertSame(200, $response->getStatusCode());
896+
self::assertSame('response for fr', $response->getContent());
897+
}
873898
}

0 commit comments

Comments
 (0)