Skip to content
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

Missing Vary Header #318

Closed
guilhermesimoes opened this issue Mar 30, 2013 · 16 comments
Closed

Missing Vary Header #318

guilhermesimoes opened this issue Mar 30, 2013 · 16 comments

Comments

@guilhermesimoes
Copy link

guilhermesimoes commented Mar 30, 2013

This bug occurs in Chrome and IE only but apparently it is a problem on Rails' end.

You can reproduce it by simply going to this page (https://secret-journey-4942.herokuapp.com/) and following the 3 steps indicated.

After hitting the back button, the browser will render the javascript returned by XHR to render the page instead of the actual page.

Here's the repository of the site that displays the bug.

This is fixed server-side by sending a Vary header, like so:

response.headers['Vary'] = 'Accept'
@guyisra
Copy link

guyisra commented Apr 2, 2013

+1

2 similar comments
@zephyr-dev
Copy link

+1

@sealabcore
Copy link

+1

@nowhereman
Copy link

This bug also occurs in IE 10 and 11.
@guilhermesimoes Can you update your demo with jQuery 1.11.x, please?
Then I could test it with IE 6, 7 and 8!

NB: Chrome 40 has still the bug.

@guilhermesimoes
Copy link
Author

So, uhh, it's been precisely 2 years since I reported this bug. Is jquery-ujs dead?

@rafaelfranca Has this been addressed in Rails? Do you know if Rails sends a Vary header? I haven't used the newest 4 releases.

@rafaelfranca
Copy link
Member

No. Rails doesn't send a Vary header. And no jquery-ujs is not dead.

All work of Rails is volunteer and we don't have time to fix all the problems that people may have. If this is a problem for you I recommend to propose a fix, or wait someone want to fix your problem.

@guilhermesimoes
Copy link
Author

It's just that getting JSON or JavaScript when pressing the back button seems like a glaring bug to me. I'm surprised that in these 2 years more people haven't had this happen to them.

I guess it's just not that common to share the exact same URL for HTML pages and API endpoints.

Anyway, thanks for chiming in @rafaelfranca. I really appreciate all your hard work on Rails ❤️

guyisra referenced this issue in rails/rails Mar 31, 2015
If someone is using ActionDispatch::Static to serve assets and makes it past the `match?` then the file exists on disk and it will be served. This PR adds in logic that checks to see if the file being served is already compressed (via gzip) and on disk, if it is it will be served as long as the client can handle gzip encoding. If not, then a non gzip file will be served.

This additional logic slows down an individual asset request but should speed up the consumer experience as compressed files are served and production applications should be delivered with a CDN. This PR allows a CDN to cache a gzip file by setting the `Vary` header appropriately. In net this should speed up a production application that are using Rails as an origin for a CDN. Non-asset request speed is not affected in this PR.
@schneems
Copy link
Member

Can anyone explain why this happens or give some specs or rational behind using "vary"? We've never set that header previously, why is this just now an issue?

@rafaelfranca
Copy link
Member

I have no checked but I can see how this happens. Let say that we requested
/tasks/1 using Ajax, and the previous page has the same url. When we click
the back button the browser tries to get the response from its cache and it
gets the javascript response. With vary we "fix" this behavior because we
are telling the browser that the url is the same but it is not from the
same type what will skip the cache.

Although this fix the behavior I don't think it is a sane default for Rails
always send vary for accept header because this can cause unneeded cache
invalidations. But I can be wrong and with a deeper investigation we can
conclude that this default makes sense.

On Tue, Mar 31, 2015, 11:59 Richard Schneeman notifications@github.com
wrote:

Can anyone explain why this happens or give some specs or rational behind
using "vary"? We've never set that header previously, why is this just now
an issue?


Reply to this email directly or view it on GitHub
#318 (comment).

@guilhermesimoes
Copy link
Author

@schneems This has been an issue for a long time. Here's the original chromium issue. There's even people specifically talking about Rails there.

Since I know that I can use a Vary header to correct this behavior, it hasn't bothered me that much. But I thought I'd ping you guys at this 2 year mark, that's all.

@JangoSteve
Copy link
Member

So, if I'm reading this correctly, it sounds like it's an issue because the browser behavior concerning cached responses for the back (and I presume forward) button changed. Just to clarify, is the only down-side or side-effect that it will cause the browser to not cache AJAX responses when hitting back/forward buttons?

@guilhermesimoes
Copy link
Author

The only downside is sending an extra header (that is useless in 99% of cases). The browser keeps caching everything as usual (though it may opt not to use the cache). Basically:

  • Without the Vary header, the browser's back button leads to rendering what the server last returned, without taking cache directives into account - which is completely nonsensical in my opinion.
  • With the Vary header, the browser takes into account the Content-Type header before deciding what to render from the cache.

In my opinion, Chrome and IE's behaviors are the ones that should change, not Rails'. We have Content-Type for a reason. But since they won't budge, Rails should either fix it or pressure them...

I honestly don't understand why that Vary header is necessary. Firefox doesn't seem to need it to render pages from its cache correctly.

@jfirebaugh
Copy link

This is pretty clearly a bug in Rails. If the response body varies according to the Accept request header, it needs to have Vary: Accept in order for downstream caches to know not to reuse cached content across different Accept values.

  • Without the Vary header, the browser's back button leads to rendering what the server last returned, without taking cache directives into account

This is false -- without the Vary: Accept header, there is no cache directive that the browser could take into account to inform it not to render what the server last returned.

  • With the Vary header, the browser takes into account the Content-Type header before deciding what to render from the cache.

Correct, which is exactly the correct and desired behavior.

I honestly don't understand why that Vary header is necessary. Firefox doesn't seem to need it to render pages from its cache correctly.

Firefox likely simply isn't being as aggressive with its caching as it is allowed to be by the spec. Chrome and IE are. If I made a browser that doesn't do any caching, its behavior doesn't inform us about what caching behavior is permissible given a particular set of headers.

@jfirebaugh
Copy link

For those interested in a spec reference, see RFC 2616 14.44:

An HTTP/1.1 server SHOULD include a Vary header field with any cacheable response that is subject to server-driven negotiation. Doing so allows a cache to properly interpret future requests on that resource and informs the user agent about the presence of negotiation on that resource.

"server-driven negotiation" is defined here and includes the Accept-based content type negotiation that we're discussing here.

@guilhermesimoes
Copy link
Author

guilhermesimoes commented May 17, 2017

This is false -- without the Vary: Accept header, there is no cache directive that the browser could take into account to inform it not to render what the server last returned.

Do you know what the Content-Type header is? Is there ever a situation in which you want JSON or JavaScript rendered back to you when you click the back button?

In any case, it seems like the Rails guys finally decided to fix this. Soon™

st0012 added a commit to st0012/rails that referenced this issue Jul 26, 2019
Problem description (quoted from @rafaelfranca's excellent explanation in rails/jquery-ujs#318 (comment)):

> Let say that we requested /tasks/1 using Ajax, and the previous page has the same url. When we click the back button the browser tries to get the response from its cache and it gets the javascript response. With vary we "fix" this behavior because we are telling the browser that the url is the same but it is not from the same type what will skip the cache.

And there's a Rails issue discussing about this problem as well rails#25842

Also, according to [RFC 7231 7.1.4](https://tools.ietf.org/html/rfc7231#section-7.1.4)

>  An origin server SHOULD send a Vary header field when its algorithm
>  for selecting a representation varies based on aspects of the request
>  message other than the method and request target

we should add `Vary: Accept` header when determining content based on the `Accept` header.

Although adding such header by default could cause unnecessary cache invalidation. But this PR only adds the header if:
- The format param is not provided
- The request is a `xhr` request
- The request has accept headers and the headers are valid

So if the user
- sends request with explicit format, like `/users/1.json`
- or sends a normal request (non xhr)
- or doesn't specify accept headers

then the header won't be added.

See the discussion in rails#25842 and
rails#36213 for more details.
@guilhermesimoes
Copy link
Author

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants