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

Skip to content

[HttpFoundation] HttpCache doesn't refresh stale responses containing an ETag #19390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
maennchen opened this issue Jul 20, 2016 · 1 comment
Closed

Comments

@maennchen
Copy link

maennchen commented Jul 20, 2016

Problem

HttpCache doesn't refresh stale responses containing an ETag and is also not able to increment the Age header of the cached response.

Affected Version

I tested the Problem on v3.1, but it seems to be around since a long time.

Scenarios

Scenario with an ETag

1st Call, Cleared Cache:

$ curl -I -XGET http://SOMEHOST/SOMEURL
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 13:19:05 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.20-1+deb.sury.org~trusty+1
Cache-Control: public, s-maxage=60
Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
Allow: GET
ETag: "6d5b1b67"
X-Content-Digest: enfb4805c151e8bb9dc08ca4464c1184d1cabd0ead565196dfb2b8e1af6b669ee3
Age: 0
X-Symfony-Cache: GET /v1/videos/12/comments: miss, store
Content-Length: 1825
Content-Type: application/hal+json

2nd Call after a few seconds (not the Age which is still 0):

$ curl -I -XGET http://SOMEHOST/SOMEURL
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 13:19:10 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.20-1+deb.sury.org~trusty+1
Cache-Control: public, s-maxage=60
vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
allow: GET
etag: "6d5b1b67"
x-content-digest: enfb4805c151e8bb9dc08ca4464c1184d1cabd0ead565196dfb2b8e1af6b669ee3
Age: 0
X-Symfony-Cache: GET /v1/videos/12/comments: fresh
content-length: 1825
Content-Type: application/hal+json

The cache will not expire and the response will never be refreshed.

Scenario without an ETag

1st Call, Cleared Cache:

$ curl -I -XGET http://SOMEHOST/SOMEURL
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 13:18:48 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.20-1+deb.sury.org~trusty+1
Cache-Control: public, s-maxage=60
Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
Allow: GET
X-Content-Digest: enfb4805c151e8bb9dc08ca4464c1184d1cabd0ead565196dfb2b8e1af6b669ee3
Age: 0
X-Symfony-Cache: GET /v1/videos/12/comments: miss, store
Content-Length: 1825
Content-Type: application/hal+json

2nd Call after a few seconds (not the Age which increments):

$ curl -I -XGET http://SOMEHOST/SOMEURL
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 13:18:57 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.20-1+deb.sury.org~trusty+1
Cache-Control: public, s-maxage=60
vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
allow: GET
x-content-digest: enfb4805c151e8bb9dc08ca4464c1184d1cabd0ead565196dfb2b8e1af6b669ee3
Age: 5
X-Symfony-Cache: GET /v1/videos/12/comments: fresh
content-length: 1825
Content-Type: application/hal+json

Cause of the Problem

The Date header of the original request is set by accident by calling this function:
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php#L426

if ($response->isCacheable()) {  // isCacheable sets the date if it is not already set.
    $this->store($request, $response);
}

isCacheable will set set date by calling isFresh, getTtl, getAge, getDate. This however does not happen if there is a ETag header.

Solution

Instead of relying on a date header which may be set by accident by a getter method, the date should explicitly be set for all cached responses. I'd propose to add the Date header if not already set in the method HttpCache::store.

https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php#L583

protected function store(Request $request, Response $response)
{
    // Change Start
    if(!$response->headers->has('Date')) {
        $response->setDate(new DateTime());
    }
    // Change End
    try {
        $this->store->write($request, $response);
        $this->record($request, 'store');
        $response->headers->set('Age', $response->getAge());
    } catch (\Exception $e) {
        $this->record($request, 'store-failed');
        if ($this->options['debug']) {
            throw $e;
        }
    }
    // now that the response is cached, release the lock
    $this->store->unlock($request);
}

Workaround for now

$response->setEtag($hash);
// @TODO: Remove as soon as an official PATCH exists for issue https://github.com/symfony/symfony/issues/19390
$response->setDate(new DateTime());
@maennchen
Copy link
Author

This seems to be a duplicate of #6746.

nicolas-grekas added a commit that referenced this issue Jul 25, 2016
…ontaining an ETag (maennchen)

This PR was merged into the 2.7 branch.

Discussion
----------

[2.7] [HttpFoundation] HttpCache refresh stale responses containing an ETag

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  |no
| BC breaks?    |no
| Deprecations? |no
| Tests pass?   | yes
| Fixed tickets | #19390, #6746
| License       | MIT
| Doc PR        |

This PR is the replacement of #19391, which points at the wrong branch.

Commits
-------

96df6b9 [HttpFoundation] HttpCache refresh stale responses containing an ETag
fabpot added a commit that referenced this issue Mar 22, 2017
This PR was squashed before being merged into the 2.8 branch (closes #22036).

Discussion
----------

Set Date header in Response constructor already

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

Setting the `Date` header in the `Response` constructor has been removed in #14912 and changed to a more lazy approach in `getDate()`.

That way, methods like `getAge()`, `getTtl()` or `isFresh()` cause side effects as they eventually call `getDate()` and the Request "starts to age" once you call them.

I don't know if this would be a nice test, but current behaviour is

```php
        $response = new Response();
        $response->setSharedMaxAge(10);
        sleep(20);
        $this->assertTrue($response->isFresh());
        sleep(5);
        $this->assertTrue($response->isFresh());
        sleep(5);
        $this->assertFalse($response->isFresh());
```

A particular weird case is the `isCacheable()` method, because it calls `isFresh()` only under certain conditions, like particular status codes, no `ETag` present etc. This symptom is also described under "Cause of the problem" in #19390, however the problem is worked around there in other ways.

So, this PR suggests to effectively revert #14912.

Additionally, I'd like to suggest to move this special handling of the `Date` header into the `ResponseHeaderBag`. If the `ResponseHeaderBag` guards that we always have the `Date`, we would not need special logic in `sendHeaders()` and could also take care of #14912 (comment).

Commits
-------

3a7fa7e Set Date header in Response constructor already
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants