Thanks to visit codestin.com
Credit goes to github.com

Skip to content

CachingHttpClient does not cache responses with missing "public" cache-control header #49937

Open
@mxr576

Description

@mxr576

Symfony version(s) affected

6.2.8

Description

I was writing a crawler for an API on Drupal.org and I tried to be nice for the provider by caching responses locally. I have followed the official documentation for enabling response caching on the HTTP client. I was surprised to see that the responses are not being cached. I dig deeper and deeper and I spotted that private Cache-Control header appears in responses, however the API backend definitely does not send that header.

I think the root cause is related to the implementation in the HTTP.

  1. ˙\Symfony\Component\HttpKernel\HttpClientKernel::handle()` builds a new response in
    $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch));
  2. Which calls \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue() marks the response are private due to the lack of explicitly specified public header value. This bubbles up the following calls...
    https://github.com/symfony/symfony/blob/v6.2.8/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php#L255-L259
  3. ...even if there is a workaround (I suppose) here for ignoring calculated headers
    $response->headers = new class($response->headers->all()) extends ResponseHeaderBag {
    protected function computeCacheControlValue(): string
    {
    return $this->getCacheControlHeader(); // preserve the original value
    }
    };

Stack trace:

image
image

How to reproduce

POC

$ php guzzle.php 
Array
(
    [0] => store, no-cache, must-revalidate, post-check=0, pre-check=0
)
$ php symfony.php 
Array
(
    [0] => store, no-cache, must-revalidate, post-check=0, pre-check=0
)
$ php symfony_with_cache.php 
Array
(
    [0] => must-revalidate, no-cache, post-check=0, pre-check=0, private, store
)

symfony.php

use Symfony\Component\HttpClient\HttpClient;

$client = HttpClient::create();

$response = $client->request('GET', 'https://updates.drupal.org/release-history/drupal/current?version=10.0.0',  [
  'headers' => [
    'Accept' => 'text/xml',
  ],
]);
print_r($response->getHeaders()['cache-control']);

symfony-with-cache.php

use Symfony\Component\HttpClient\CachingHttpClient;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpKernel\HttpCache\Store;

$client = HttpClient::create();
$client = new CachingHttpClient($client, new Store(__DIR__ . '/cache'));

$response = $client->request('GET', 'https://updates.drupal.org/release-history/drupal/current?version=10.0.0',  [
  'headers' => [
    'Accept' => 'text/xml',
  ],
]);
print_r($response->getHeaders()['cache-control']);

Possible Solution

No response

Additional Context

The root cause of the issue can be the interpretation of https://www.rfc-editor.org/rfc/rfc7234#section-3.

Only one of these is required, so s-maxage should not be required on a GET request with HTTP 200 response.

the response either:

  *  contains an Expires header field (see [Section 5.3](https://www.rfc-editor.org/rfc/rfc7234#section-5.3)), or

  *  contains a max-age response directive (see [Section 5.2.2.8](https://www.rfc-editor.org/rfc/rfc7234#section-5.2.2.8)), or

  *  contains a s-maxage response directive (see [Section 5.2.2.9](https://www.rfc-editor.org/rfc/rfc7234#section-5.2.2.9))
     and the cache is shared, or

  *  contains a Cache Control Extension (see [Section 5.2.3](https://www.rfc-editor.org/rfc/rfc7234#section-5.2.3)) that
     allows it to be cached, or

  *  has a status code that is defined as cacheable by default (see
     [Section 4.2.2](https://www.rfc-editor.org/rfc/rfc7234#section-4.2.2)), or

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions