1 bootstrap.inc | backdrop_serve_page_from_cache(stdClass $cache) |
Sets HTTP headers in preparation for a cached page response.
The headers allow as much as possible in proxies and browsers without any particular knowledge about the pages. Modules can override these headers using backdrop_add_http_header().
If the request is conditional (using If-Modified-Since and If-None-Match), and the conditions match those currently in the cache, a 304 Not Modified response is sent.
File
- core/
includes/ bootstrap.inc, line 1731 - Functions that need to be loaded on every Backdrop request.
Code
function backdrop_serve_page_from_cache(stdClass $cache) {
$page_content = $cache->data['body'];
// Negotiate whether to use compression.
$page_compression = !empty($cache->data['page_compressed']);
$return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
// Get headers set in hook_boot(). Keys are lower-case.
$hook_boot_headers = backdrop_get_http_header();
// Headers generated in this function, that may be replaced or unset using
// backdrop_add_http_headers(). Keys are mixed-case.
$default_headers = array();
foreach ($cache->data['headers'] as $name => $value) {
// In the case of a 304 response, certain headers must be sent, and the
// remaining may not (see RFC 2616, section 10.3.5). Do not override
// headers set in hook_boot().
$name_lower = strtolower($name);
if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) {
backdrop_add_http_header($name, $value);
unset($cache->data['headers'][$name]);
}
}
// If the client sent a session cookie, a cached copy will only be served
// to that one particular client due to Vary: Cookie. Thus, do not set
// max-age > 0, allowing the page to be cached by external proxies, when a
// session cookie is present unless the Vary header has been replaced or
// unset in hook_boot().
$max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? config_get('system.core', 'page_cache_maximum_age') : 0;
$default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
// Entity tag should change if the output changes. Quotes are required per
// header specification:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
$etag = '"' . $cache->created . ($return_compressed ? '-gzip' : '') . '"';
header('Etag: ' . $etag);
// See if the client has provided the required HTTP headers.
$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
// Handle an exact match based on Etag. The If-None-Match header takes full
// precedence over If-Modified-Since header.
if ($if_none_match == $etag) {
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
backdrop_send_headers($default_headers);
return;
}
// Handle a loose match based on date only, if no Etag was provided.
if (!$if_none_match && $if_modified_since && $if_modified_since >= $cache->created) {
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
backdrop_send_headers($default_headers);
return;
}
// Send the remaining headers.
foreach ($cache->data['headers'] as $name => $value) {
backdrop_add_http_header($name, $value);
}
$default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created);
// HTTP/1.0 proxies does not support the Vary header, so prevent any caching
// by sending an Expires date in the past. HTTP/1.1 clients ignores the
// Expires header if a Cache-Control: max-age= directive is specified (see RFC
// 2616, section 14.9.3).
$default_headers['Expires'] = 'Fri, 16 Jan 2015 07:50:00 GMT';
backdrop_send_headers($default_headers);
// Allow HTTP proxies to cache pages for anonymous users without a session
// cookie. The Vary header is used to indicates the set of request-header
// fields that fully determines whether a cache is permitted to use the
// response to reply to a subsequent request for a given URL without
// revalidation. If a Vary header has been set in hook_boot(), it is assumed
// that the module knows how to cache the page.
if (!isset($hook_boot_headers['vary']) && !settings_get('omit_vary_cookie')) {
header('Vary: Cookie');
}
if ($page_compression) {
header('Vary: Accept-Encoding', FALSE);
// If page_compression is enabled, the cache contains gzipped data.
if ($return_compressed) {
// $cache->data['body'] is already gzip'ed, so make sure
// zlib.output_compression does not compress it once more.
ini_set('zlib.output_compression', '0');
header('Content-Encoding: gzip');
}
else {
// The client does not support compression, so unzip the data in the
// cache. Strip the gzip header and run uncompress.
$page_content = gzinflate(substr(substr($page_content, 10), 0, -8));
}
}
// Send the final headers that indicate the page total length, after the
// decompression of the gzipped HTML.
$page_length = strlen($page_content);
backdrop_set_length_headers($page_length);
// Print the page. If doing an HTTP HEAD request on some servers like
// Apache, it will abort PHP immediately once content has been returned
// (because no additional headers would be allowed after that point). So we
// print nothing for HEAD requests so that background processing may continue
// if needed. Note that the content is still saved in the page cache even
// though it is not printed to the page. See backdrop_page_footer().
if ($_SERVER['REQUEST_METHOD'] !== 'HEAD') {
print $page_content;
}
}