Issue 1:
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());

Issue 2:
I am debugging a Symfony 2.1 app using the HttpCache and it seems to me that in every request the age remains at 0. imho we need to set a date in the response inside HttpCache::store() before we call write() to ensure that on a subsequent request we can properly compute the age of the response.