3

I've got a pretty complex webapp based on Django 1.11. Some time ago users started reporting that they are getting 'someone else's views' - memcached provided them with html cached by decorator @cache_page(xx) without distinguishing between sessions within the cache grace period.

Upon further investigation, I discovered that in some cases Vary: Cookie header was missing and wrong 'session' was served. What's strange, it only showed when querying backend with curl (which has no session, user etc -> backend served logged in cached view).

Unfortunately, this issue is really hard to reproduce, sometimes it occures, sometimes it doesn't. I even build a simple Django app from scratch to see if I could check what is the cause. What was observed, is that the issue does not occur when @cache_page is removed or login_required is added .

I ended up removing all @cache_page decorators from views and the issue was not observed on production since but it's a workaround and I would like to know what is the cause.

If anyone has any hint what could be the cause, it would be greatly appreciated!

Brachacz
  • 156
  • 1
  • 4

1 Answers1

2

You're probably running into this open bug:

Since view decorators run on the outgoing response first, before response middleware, the cache_page decorator caches the response before any of the mentioned response middlewares have a chance to add their Vary headers. This means two things: 1) the cache key used won't include the headers the response ought to vary on, and Django may later serve that response to users who really shouldn't get it, and 2) when that cached response is later served to a user, it still won't include the Vary header that it should have, and thus may also be cached wrongly by an upstream HTTP cache.

In other words, at the time that the response is cached the SessionMiddleware hasn't yet had a chance to set the Vary: Cookie header, so all sessions will share the same cache key.

You can probably work around this by specifying the Vary header explicitly. For example:

from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie

@cache_page()
@vary_on_cookie()
def my_view():
    pass
Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102