142
votes

I'm trying to use http caching. In my controller I'm setting a response as follows:

$response->setPublic();
$response->setMaxAge(120);
$response->setSharedMaxAge(120);
$response->setLastModified($lastModifiedAt);

dev mode

In dev environment first response is a 200 with following headers:

cache-control:max-age=120, public, s-maxage=120
last-modified:Wed, 29 Feb 2012 19:00:00 GMT

For next 2 minutes every response is a 304 with following headers:

cache-control:max-age=120, public, s-maxage=120

This is basically what I expect it to be.

prod mode

In prod mode response headers are different. Note that in app.php I wrap the kernel in AppCache.

First response is a 200 with following headers:

cache-control:must-revalidate, no-cache, private
last-modified:Thu, 01 Mar 2012 11:17:35 GMT

So it's a private no-cache response.

Every next request is pretty much what I'd expect it to be; a 304 with following headers:

cache-control:max-age=120, public, s-maxage=120

Should I worry about it? Is it an expected behaviour?

What will happen if I put Varnish or Akamai server in front of it?

I did a bit of debugging and I figured that response is private because of last-modified header. HttpCache kernel uses EsiResponseCacheStrategy to update the cached response (HttpCache::handle() method).

if (HttpKernelInterface::MASTER_REQUEST === $type) {
    $this->esiCacheStrategy->update($response);
}

EsiResponseCacheStrategy turns a response into non cacheable if it uses either Last-Response or ETag (EsiResponseCacheStrategy::add() method):

if ($response->isValidateable()) {
    $this->cacheable = false;
} else {
    // ... 
}

Response::isValidateable() returns true if Last-Response or ETag header is present.

It results in overwriting the Cache-Control header (EsiResponseCacheStrategy::update() method):

if (!$this->cacheable) {
    $response->headers->set('Cache-Control', 'no-cache, must-revalidate');

    return;
}

I asked this question on Symfony2 user group but I didn't get an answer so far: https://groups.google.com/d/topic/symfony2/6lpln11POq8/discussion

Update.

Since I no longer have access to the original code I tried to reproduce the scenario with the latest Symfony standard edition.

Response headers are more consistent now, but still seem to be wrong.

As soon as I set a Last-Modified header on the response, the first response made by a browser has a:

Cache-Control:must-revalidate, no-cache, private

Second response has an expected:

Cache-Control:max-age=120, public, s-maxage=120

If I avoid sending If-Modified-Since header, every request returns must-revalidate, no-cache, private.

It doesn't matter if the request was made in prod or dev environment anymore.

2
when i disable the $kernel = new AppCache($kernel); it is showed as public to me. but then it will always response with a code 200 ... i use as a revery proxy nginx.Michael
are your app.php and app_dev.php the same ? (ignoring debug and env)Florian Klein
I have no access to that project anymore so I can't confirm this. I remember controllers were default ones with AppCache enabled.Jakub Zalas
@Florian I tried reproducing the problem and I've got a bit different behaviour with the latest Symfony version (see an update).Jakub Zalas
Would you set debug=>true into getOptions() in AppCache so that you get X-Symfony-Cache header?denkiryokuhatsuden

2 Answers

9
votes

I have faced same problem. I had to supply 'public' headers my cdn. By default when gateway caching is enabled in prod mode, it returns 200 OK with private, nocache must validate headers.

I solved problem this way.

In app.php, before I send response to user ($respond->send), I have overwritten the cache control header to blank and set cache headers to public and max age(some value).

//code snippet from app.php

    $response = $kernel->handle($request);
    $response->headers->set('Cache-Control', '');
    $response->setPublic();
    $response->setMaxAge(86400);
    $response->send();        
-4
votes

The behavior you experience is intended. Symfony2 Docs explicitly describe the situations when private and public are used, default being private.