Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Setting the Expires header to any value using drupal_add_http_header()
causes the page caching subsystem to fail, always causing misses and sending X-Drupal-Cache: MISS
headers.
This happens regardless of whether you set it to a value or unset it using FALSE.
This happens regardless of whether it is called in hook_boot
, hook_init
, or hook_page_alter
.
Comments
Comment #1
Steven Merrill CreditAttribution: Steven Merrill commentedThis occurs on Drupal 7.22 and 7.26.
Comment #2
bmcmurray CreditAttribution: bmcmurray commentedI've done some sleuthing and here is what I think is happening:
The function drupal_page_set_cache() (includes/common.inc), specifically lines 5187:5190, checks for the ‘Expires’ header and then calls strtotime() on the value of that, expecting the value to be a string that can be converted to a time. Even though the documentation for drupal_get_http_header() (https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drup...) states that it’s valid to set a header value to FALSE to unset it, the Expires header appears to be an undocumented special case. Calling strtotime(FALSE) returns a non-numeric (non-unix timestamp) result, causing the cache set to have no lifetime, essentially allowing it to immediately expire. Memcache allows an expire time of 0 to be set to store an item in the cache “permanently” (as long as possible until pushed out of memory or force flushed) -- http://www.php.net/manual/en/memcache.set.php. Attempting to set the Expires header to 0 does not resolve this, but instead causes the Expires header to appear in returns as: Expires: 0 and strtotime(0) returns a non-numeric result.
Comment #3
Steven Merrill CreditAttribution: Steven Merrill commentedComment #4
Alexander Allen CreditAttribution: Alexander Allen commentedMy observations are exactly the same as @bmcmurray. While using the FALSE as a second parameter does work as advertised by drupal_add_http_header() for most headers, there is that particular use case inside
drupal_page_set_cache()
where if one particular header is namedexpires
, the value of$cache->expire = strtotime($value);
becomesstrtotime(FALSE)
, which in turns ends up setting$cache->expire
as FALSE. So the header is removed, but the page cache is defeated and a full drupal bootstrap / render done for the request. Here are the relevant drupal_page_set_cache() lines for reference:Also confirming smerrill's observation that setting the value to an integer on
drupal_add_http_header()
doesn't solve the issue (something I was hoping for).