Affecting The Vary Header in Drupal 7 Cached Pages

I came up against a situation I imagine is not very unique, but is very frustrating for a client today. Thanks to updates in Google's algorithms we're adding an extra header Vary to all of our sites that run multiple themes based on user agent detection. It my case, it is the theme switching, but if you vary even for little things, it's not a bad idea to declare that variance. You not even be varying by User-Agent. But, if you hit the Drupal 7 caching layer, you're going to have a bad time.

Everything looked like it worked on my local, which of course runs in a development mode with caching off. So when I turned on caching to check, the real fun started. It seems I was able to declare the extra variant header at the theme layer, but it just would not output once cached. Investigation into the actual cache_page table showed that the Vary: User-Agent header was associated! So what gives? The issue was actually in the bootstrap.inc file and drupal_serve_page_from_cache function, in the way cached pages are served. I'll snippet the relevant portions:

  1.   // Allow HTTP proxies to cache pages for anonymous users without a session
  2.   // cookie. The Vary header is used to indicates the set of request-header
  3.   // fields that fully determines whether a cache is permitted to use the
  4.   // response to reply to a subsequent request for a given URL without
  5.   // revalidation. If a Vary header has been set in hook_boot(), it is assumed
  6.   // that the module knows how to cache the page.
  7.   if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie')) {
  8.     header('Vary: Cookie');
  9.   }
  10.  
  11.   if ($page_compression) {
  12.     header('Vary: Accept-Encoding', FALSE);
  13.   ...

So, if you do not specifically state a vary in your hook_boot, or start specifically omitting the cookie variance, it doesn't matter what vary header you cache, it's going to smash it, and start over. There are only a few solutions to this problem:

  1. Create a local version of bootstrap.inc using the settings file to point to the updated version

    This is one step above modifying core, but promises headaches down the road. No.
  2. Set the variable to omit cookie variance control.

    Loss of control? No.
  3. Use a custom module, loaded at boot level, to declare a vary header.

    This is the least evil of the solutions. Go with it

In custom.module, create the follow hook:

  1. /**
  2.  * Implemenation of hook_boot
  3.  */
  4. function custom_boot(){
  5.   // Because we're swapping content based on user agent
  6.   // we have to tell the cache layer we've modified
  7.   // the vary header manually
  8.   // see: bootstrap.inc:drupal_serve_page_from_cache()
  9.   drupal_add_http_header('vary', 'Cookie,User-Agent');
  10. }

You'll also need to register your module as a boot module. This can be done at install using this hook in the custom.install file:

  1. function custom_install(){
  2.   db_query("UPDATE {system} SET bootstrap = 1 WHERE name = 'custom'");
  3. }

But, if it's already running? Clear cache, and run the same command against the database.

Tags: 

Comments

This can be done more quickly, and far more easily through .htaccess. I'm aware. But, if you should have some reason you can't use it, this is a nice fix.

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.