8000 Multiple Cache-Control headers sent in 4.0.0-BETA4 · Issue #24988 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
8000

Multiple Cache-Control headers sent in 4.0.0-BETA4 #24988

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
aliechti opened this issue Nov 16, 2017 · 29 comments
Closed

Multiple Cache-Control headers sent in 4.0.0-BETA4 #24988

aliechti opened this issue Nov 16, 2017 · 29 comments

Comments

@aliechti
Copy link
aliechti commented Nov 16, 2017
Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Symfony version 4.0.0-BETA4

This is since this commit: cf4eb46

Because of this change the Cache-Control header is sent always. As far as I know the session.cache_limiter off state is '' and not '0'.
http://php.net/manual/en/function.session-cache-limiter.php

@sroze
Copy link
Contributor
sroze commented Nov 16, 2017

Setting the cache limiter to '' will turn off automatic sending of cache headers entirely.

From the PHP documentation perspective, it looks like you are correct. One for @nicolas-grekas, I don't get why we've made this change in Symfony 🤔

@sroze
Copy link
Contributor
sroze commented Nov 16, 2017

Actually, all you have to do, is to set the Symfony session storage cache_limiter configuration to 0 as described in the commit you've pointed out.

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Nov 19, 2017

It's true that multiple Cache-Control headers are sent. Yet this is allowed by the HTTP spec so not a bug in itself.
What's the issue actually? Even a simple session->has() makes the HTTP resource uncacheable, so it is actually accurate to send this header, isn't it?

@aliechti
Copy link
Author
aliechti commented Nov 20, 2017

The issue is, why is this setting set to '0' and not ''?
Because default is '', you can't cache via http without setting the config like this:

# config/packages/framework.yaml
parameters:
    session.storage.options:
        cache_limiter: 0

@nicolas-grekas
Copy link
Member

why is this setting set to '0' and not ''?

it's been always documented like that (this is a Symfony setting, not a PHP one, so we can derivate a bit from the PHP doc actually.)
is putting 0 fixing your issue?

@aliechti
Copy link
Author

Yes it fixes it, but it looks more like a workaround to a strange default behavior.

@nicolas-grekas
Copy link
Member

OK, nice.
Sending the HTTP header by default is correct IMHO - not sending it is strange...
Closing as discussing what is "strange" is relative, and the discussion has resolved that using 0 allows meeting your expectation.

@goetas
Copy link
Contributor
goetas commented Nov 30, 2017

@xabbuh can this be documented somehow? yesterday I've spent 3 hours to find how to set the cache_limiter... I did a compiler pass to edit that parameter.... did not know that session.storage.options parameter could help... (at the end was really frustrated)

documenting this config as example...

# config/packages/framework.yaml
parameters:
    session.storage.options:
        cache_limiter: 0

@xabbuh
Copy link
Member
xabbuh commented Nov 30, 2017

@goetas Would you mind opening an issue in the docs repo (best search before if we already have one)?

@nicolas-grekas
Copy link
Member

Note that the doc should be clear that this is not recommended, as this will generate corrupted http caches.

@goetas
Copy link
Contributor
goetas commented Nov 30, 2017

@nicolas-grekas yes I'm aware of it, I was using in mu particular case private_no_expire

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 15, 2017

I think there are serious implications here. I too spent 2 hours figuring out why Chrome and Safari are not respecting my Cache-Control settings.

In my Response I set the following to avoid 304 Not Modified requests/responses because I am serving a file which is never going to change in the future:

$response
    ->setImmutable()
    ->setMaxAge(604800)
    ->setPublic()

However both Chrome and Safari kept on sending requests. After inspecting the headers I noticed two Cache-Control headers:

Cache-Control:max-age=0, private, must-revalidate
Cache-Control:immutable, max-age=604800, public

The first header is sent by Symfony, the second one because of my Response. But it seems the browsers do not discard the must-revalidate option and thus triggers If-Modified-Since requests with 304 Not Modified responses.

After manually commenting out the Symfony header it all worked out fine.

If I change cache_limiter parameter it will apply to all my responses but I want to disable it only for this specific response where I need to control the Cache-Control headers.

Any ideas?

@nicolas-grekas
Copy link
Member

See #25448, it contains everything on the topic.

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 15, 2017

Thanks for pointing me to that issue. You say there:

If you want to opt-out from sending the header, you need to set the 'cache_limiter' option to '0'.

This is want I want BUT only for this specific Response (otherwise browsers will not cache to response as intended). For all other responses the current Symfony default (cache_limiter='') is perfectly fine.

How to accomplish that?

@nicolas-grekas
Copy link
Member

Not sure you can.
But instead of looking for that, I'd suggest to look why is the session used on that page.

@goetas
Copy link
Contributor
goetas commented Dec 15, 2017

This is want I want BUT only for this specific Response (otherwise browsers will not cache to response as intended)

I also had the same need, it was per url/path

(serving cacheable images only to authorized users was the case...)
This replies to:

But instead of looking for that, I'd suggest to look why is the session used on that page.

The cache was in-browser

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 15, 2017

(serving cacheable images only to authorized users was the case...)

Same here, only authorized users may access the static media files which is not an uncommon scenario.

Since upgrading Symfony 3.3 to 3.4 our number of requests has increased by 60% due to this.

A nice solution would be to set these headers in the Response class by default instead of using the header() function so that they can be overwritten per response when needed. There is currently no way to undo the must-revalidate now and the behaviour is difficult to control with the cache_limiter parameter.

A workaround for me could be to use cache_limiter='0' and add a ResponseListener which adds the default Cache-Control header when not specified in the response. But the above approach seems ideal to me.

Any thoughts on this?

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 16, 2017

I found a work-around for my use case. I added

header('Cache-Control: immutable, max-age=31536000, public', true);

before returning the response, which overwrites the Symfony header() for that specific response forcing my wanted Cache-Control header. Very nasty though.

I really believe Symfony should avoid using the header() function and place the headers in the Response instead so it can be overwritten.

@nicolas-grekas
Copy link
Member

I'm sorry but I still fail to understand. As soon as a resource is public, it can be served directly to next users, bypassing any authentication.
This conflicts with the fact that the session is read, which means that the resource is possibly different for some/any users.
So again, why are you accessing the session in the first place to render immutable resources?
Hacking the CC header looks like a workaround that hides a deeper problem on your side...

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 16, 2017

The session is needed because these static media files may only be served to authenticated users.

public indicates the content may be cached by intermediate caches, which is what I want to further reduce traffic. It may as well be private, that is not the real issue here.

The real issue is that the default must-revalidate as indicated by Symfony cannot be changed by the programmer anymore. There is no option to overwrite it with a Cache-Control header to undo its meaning. So the only option available now is to use the cache_limiter='0' but this causes all sorts of other caching issues which must be addressed by the programmer.

This is the Symfony header:

Cache-Control: max-age=%d, private, must-revalidate

Both max-age and private can be overwritten, but must-revalidate cannot be undone so there is no way to avoid 304's then.

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Dec 16, 2017

The session is needed because these static media files may only be served to authenticated users.

This makes no sense to me. As soon as a resource is served by a reverse proxy- which "public" allows, it cannot be authenticated anymore using the session. How do you resolve that?

@RonnieRocket147
Copy link

So I change public to private which probably makes more sense, I agree with you.

Then my original point still stands: 304's can't be avoided by the default Symfony header() usage without resorting to cache_limiter='0' with all its implications or my header() overwrite hack.

Do you understand my issue here?

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Dec 16, 2017

I understand your issue, but I'm challenging it's reality. We just agreed that private is correct and public is buggy.
Now revalidate: how do you ensure that the authenticated resource is not reused after logging out?

@RonnieRocket147
Copy link
RonnieRocket147 commented Dec 16, 2017

I can't ensure that so it can be reused even if the user is logged out. This is no different than the must-revalidate before it becomes stale as per RFC:

If the response includes the "must-revalidate" cache-control directive, the cache MAY use that response in replying to a subsequent request

Do you agree it would be more flexible and Symfony-like to set these headers on the Response object instead of using the header() function?

@goetas
Copy link
Contributor
goetas commented Dec 16, 2017

@nicolas-grekas as explained here, this is one of many thickets dealing with a similar issue...
all ot them pointing to the fact that symfony is using the header function instead of the Response object.

Using the header function forces us to use also that function to override the symfony's assumptions on how sessions should be treated... there will be always a case not covered.

@nicolas-grekas
Copy link
Member

@goetas you're right, that's how it should be. That's not how it works today, so PR welcome. I'm not in a hurry to fix this myself, especially as it looks like getting correct CC headers is hard/easy to get wrong (see above.) By providing full flexibility, a new solution should also provide guidance to correct semantic.

@goetas
Copy link
Contributor
goetas commented Dec 16, 2017

@nicolas-grekas Great to hear! Was interested to know what is the general direction!

@mvrhov
Copy link
mvrhov commented Dec 16, 2017

Why does cache_limiter require a special parameter and doesn't use the same behavior as other parameters e.g.

framework:
    session:
        gc_maxlifetime: 7200
        save_path:      '%kernel.root_dir%/var/session/'
        name:           'sid'
        cache_limiter:  0

@Toflar
Copy link
Contributor
Toflar commented Dec 22, 2017

Although I agree with @nicolas-grekas that if you start a session a response should be considered to be private, the implementation like it is now is inconsistent.

I think we must always suppress the NativeSessionStorage from generating any headers by default. Otherwise the Cache-Control header never makes it to the proper Symfony HttpFoundation Response object and is thus missed by kernel.response listeners and for example the Symfony HttpCache. So depending on whether you use Symfony's HttpCache or Varnish as a reverse proxy, caching would be handled differently. Varnish would consider the response to be private. HttpCache would not. That's inconsistent.

To fix this properly, imho we should do the following:

  • Always prevent the NativeSessionStorage from generating headers by default.
  • Adjust the SaveSessionListener to not only store the session when started but also setting the response to private when started.

This would result in a consistent behaviour no matter what session storage or reverse proxy you use.

fabpot added a commit that referenced this issue Dec 31, 2017
…g raw header() when session is started (Toflar)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started

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

As described in #24988 I think the current handling of the `Cache-Control` header set by the `NativeSessionStorage` causes inconsistent behaviour.

In #24988 @nicolas-grekas states that if you start a session a response should be considered to be private. I do agree with this but up until now, nobody takes care of this on `kernel.response`.

I think we must always suppress the `NativeSessionStorage` from generating any headers by default. Otherwise the `Cache-Control` header never makes it to the `Response` instance and is thus missed by `kernel.response` listeners and for example the Symfony HttpCache. So depending on whether you use Symfony's HttpCache  or Varnish as a reverse proxy, caching would be handled differently.  Varnish would consider the response to be private if you set the php.ini setting `session.cache_limiter` to `nocache` (which is default) because it will receive the header. HttpCache would not because the `Cache-Control` header is not present on the `Response`.  That's inconsistent and may cause confusion or problems when switching proxies.

Commits
-------

dbc1c1c [HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started
symfony-splitter pushed a commit to symfony/http-kernel that referenced this issue Dec 31, 2017
…g raw header() when session is started (Toflar)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | symfony/symfony#24988
| License       | MIT
| Doc PR        | -

As described in #24988 I think the current handling of the `Cache-Control` header set by the `NativeSessionStorage` causes inconsistent behaviour.

In #24988 @nicolas-grekas states that if you start a session a response should be considered to be private. I do agree with this but up until now, nobody takes care of this on `kernel.response`.

I think we must always suppress the `NativeSessionStorage` from generating any headers by default. Otherwise the `Cache-Control` header never makes it to the `Response` instance and is thus missed by `kernel.response` listeners and for example the Symfony HttpCache. So depending on whether you use Symfony's HttpCache  or Varnish as a reverse proxy, caching would be handled differently.  Varnish would consider the response to be private if you set the php.ini setting `session.cache_limiter` to `nocache` (which is default) because it will receive the header. HttpCache would not because the `Cache-Control` header is not present on the `Response`.  That's inconsistent and may cause confusion or problems when switching proxies.

Commits
-------

dbc1c1c [HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this issue Dec 31, 2017
…g raw header() when session is started (Toflar)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | symfony/symfony#24988
| License       | MIT
| Doc PR        | -

As described in #24988 I think the current handling of the `Cache-Control` header set by the `NativeSessionStorage` causes inconsistent behaviour.

In #24988 @nicolas-grekas states that if you start a session a response should be considered to be private. I do agree with this but up until now, nobody takes care of this on `kernel.response`.

I think we must always suppress the `NativeSessionStorage` from generating any headers by default. Otherwise the `Cache-Control` header never makes it to the `Response` instance and is thus missed by `kernel.response` listeners and for example the Symfony HttpCache. So depending on whether you use Symfony's HttpCache  or Varnish as a reverse proxy, caching would be handled differently.  Varnish would consider the response to be private if you set the php.ini setting `session.cache_limiter` to `nocache` (which is default) because it will receive the header. HttpCache would not because the `Cache-Control` header is not present on the `Response`.  That's inconsistent and may cause confusion or problems when switching proxies.

Commits
-------

dbc1c1c4b6 [HttpKernel] Call Response->setPrivate() instead of sending raw header() when session is started
symfony-splitter pushed a commit to symfony/http-kernel that referenced this issue Mar 30, 2018
…rivate automatically (Toflar)

This PR was squashed before being merged into the 4.1-dev branch (closes #26681).

Discussion
----------

Allow to easily ask Symfony not to set a response to private automatically

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

This PR is related to the many discussions going on about the latest (Symfony 3.4+) changes regarding the way Symfony handles the session. I think we're almost there now, Symfony 4.1 automatically turns responses into `private` responses once the session is started and it's all done in the `AbstractSessionListener` where the session is also saved.
In other issues/PRs (symfony/symfony#25583, symfony/symfony#25699, symfony/symfony#24988) it was agreed that setting the response to `private` if the session is started is a good default for Symfony. It was also agreed that setting it to `private` does not always make sense because you **can share a response** across sessions, it just requires a more complex caching setup with shared user context etc.
So there must be an easy way to disable this behaviour. Right now it's very hard to do so because what you end up doing is basically decorating the `session_listener` which is very hard because you have to keep track on that over different Symfony versions as the base listener might get additional features etc.

The [FOSCacheBundle](FriendsOfSymfony/FOSHttpCacheBundle#438) is already having this problem, [Contao](contao/core-bundle#1388) has the same issue and there will be probably more. Basically everyone that wants to share a response cache across the session will have to decorate the default listener. That's just too hard, so I came up with this solution. The header is easy. Every project can add that easily. It does not require any extension, configuration or adjustment of any service. It's clean, transparent and has absolutely no impact on "default" Symfony setups.

Would be happy to have some feedback before 4.1 freeze.

Commits
-------

0f36710 Allow to easily ask Symfony not to set a response to private automatically
fabpot added a commit that referenced this issue Mar 30, 2018
…rivate automatically (Toflar)

This PR was squashed before being merged into the 4.1-dev branch (closes #26681).

Discussion
----------

Allow to easily ask Symfony not to set a response to private automatically

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

This PR is related to the many discussions going on about the latest (Symfony 3.4+) changes regarding the way Symfony handles the session. I think we're almost there now, Symfony 4.1 automatically turns responses into `private` responses once the session is started and it's all done in the `AbstractSessionListener` where the session is also saved.
In other issues/PRs (#25583, #25699, #24988) it was agreed that setting the response to `private` if the session is started is a good default for Symfony. It was also agreed that setting it to `private` does not always make sense because you **can share a response** across sessions, it just requires a more complex caching setup with shared user context etc.
So there must be an easy way to disable this behaviour. Right now it's very hard to do so because what you end up doing is basically decorating the `session_listener` which is very hard because you have to keep track on that over different Symfony versions as the base listener might get additional features etc.

The [FOSCacheBundle](FriendsOfSymfony/FOSHttpCacheBundle#438) is already having this problem, [Contao](contao/core-bundle#1388) has the same issue and there will be probably more. Basically everyone that wants to share a response cache across the session will have to decorate the default listener. That's just too hard, so I came up with this solution. The header is easy. Every project can add that easily. It does not require any extension, configuration or adjustment of any service. It's clean, transparent and has absolutely no impact on "default" Symfony setups.

Would be happy to have some feedback before 4.1 freeze.

Commits
-------

0f36710 Allow to easily ask Symfony not to set a response to private automatically
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

9 participants
0