Issue #2257709 by znerol, Wim Leers: Remove the interdependence between the internal page cache and management of the Cache-Control header for external caches.
parent
4b68f9afe4
commit
5a01a26f15
|
@ -581,7 +581,7 @@ services:
|
|||
class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@language_manager']
|
||||
arguments: ['@language_manager', '@config.factory']
|
||||
scope: request
|
||||
redirect_response_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\RedirectResponseSubscriber
|
||||
|
|
|
@ -650,26 +650,27 @@ function drupal_page_cache_get_cid(Request $request) {
|
|||
/**
|
||||
* Retrieves the current page from the cache.
|
||||
*
|
||||
* Note: we do not serve cached pages to authenticated users, or to anonymous
|
||||
* users when $_SESSION is non-empty. $_SESSION may contain status messages
|
||||
* from a form submission, the contents of a shopping cart, or other user-
|
||||
* specific content that should not be cached and displayed to other users.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request for this page.
|
||||
*
|
||||
* @return
|
||||
* The cache object, if the page was found in the cache, NULL otherwise.
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response, if the page was found in the cache, NULL otherwise.
|
||||
*/
|
||||
function drupal_page_get_cache(Request $request) {
|
||||
if (drupal_page_is_cacheable()) {
|
||||
return \Drupal::cache('render')->get(drupal_page_cache_get_cid($request));
|
||||
$cache = \Drupal::cache('render')->get(drupal_page_cache_get_cid($request));
|
||||
if ($cache) {
|
||||
return $cache->data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the cacheability of the current page.
|
||||
*
|
||||
* Note: we do not serve cached pages to authenticated users, or to anonymous
|
||||
* users when $_SESSION is non-empty. $_SESSION may contain status messages
|
||||
* from a form submission, the contents of a shopping cart, or other user-
|
||||
* specific content that should not be cached and displayed to other users.
|
||||
*
|
||||
* @param $allow_caching
|
||||
* Set to FALSE if you want to prevent this page to get cached.
|
||||
*
|
||||
|
@ -818,7 +819,7 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE)
|
|||
* fresh page on every request. This prevents authenticated users from seeing
|
||||
* locally cached pages.
|
||||
*
|
||||
* Also give each page a unique ETag. This will force clients to include both
|
||||
* Also give each page a unique ETag. This should force clients to include both
|
||||
* an If-Modified-Since header and an If-None-Match header when doing
|
||||
* conditional requests for the page (required by RFC 2616, section 13.3.4),
|
||||
* making the validation more robust. This is a workaround for a bug in Mozilla
|
||||
|
@ -868,93 +869,59 @@ function drupal_page_header() {
|
|||
* and the conditions match those currently in the cache, a 304 Not Modified
|
||||
* response is sent.
|
||||
*/
|
||||
function drupal_serve_page_from_cache(stdClass $cache, Response $response, Request $request) {
|
||||
$config = \Drupal::config('system.performance');
|
||||
function drupal_serve_page_from_cache(Response $response, Request $request) {
|
||||
// Only allow caching in the browser and prevent that the response is stored
|
||||
// by an external proxy server when the following conditions apply:
|
||||
// 1. There is a session cookie on the request.
|
||||
// 2. The Vary: Cookie header is on the response.
|
||||
// 3. The Cache-Control header does not contain the no-cache directive.
|
||||
if ($request->cookies->has(session_name()) &&
|
||||
in_array('Cookie', $response->getVary()) &&
|
||||
!$response->headers->hasCacheControlDirective('no-cache')) {
|
||||
|
||||
// First half: we must determine if we should be returning a 304.
|
||||
$response->setPrivate();
|
||||
}
|
||||
|
||||
// Negotiate whether to use compression.
|
||||
$page_compression = !empty($cache->data['page_compressed']) && extension_loaded('zlib');
|
||||
$return_compressed = $page_compression && $request->server->has('HTTP_ACCEPT_ENCODING') && strpos($request->server->get('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE;
|
||||
|
||||
// Get headers. Keys are lower-case.
|
||||
$boot_headers = drupal_get_http_header();
|
||||
|
||||
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).
|
||||
$name_lower = strtolower($name);
|
||||
if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($boot_headers[$name_lower])) {
|
||||
$response->headers->set($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.
|
||||
$max_age = !$request->cookies->has(session_name()) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0;
|
||||
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
|
||||
|
||||
// Entity tag should change if the output changes.
|
||||
$response->setEtag($cache->created);
|
||||
|
||||
// See if the client has provided the required HTTP headers.
|
||||
$if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
|
||||
$if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
|
||||
|
||||
if ($if_modified_since && $if_none_match
|
||||
&& $if_none_match == $response->headers->get('etag') // etag must match
|
||||
&& $if_modified_since == $cache->created) { // if-modified-since must match
|
||||
$response->setStatusCode(304);
|
||||
return;
|
||||
}
|
||||
|
||||
// Second half: we're not returning a 304, so put in other headers.
|
||||
|
||||
// Send the remaining headers.
|
||||
foreach ($cache->data['headers'] as $name => $value) {
|
||||
$response->headers->set($name, $value);
|
||||
drupal_add_http_header($name, $value);
|
||||
}
|
||||
|
||||
$response->setLastModified(\DateTime::createFromFormat('U', $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).
|
||||
if (!$response->getExpires()) {
|
||||
$response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
|
||||
}
|
||||
|
||||
// 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 (!isset($boot_headers['vary']) && !Settings::get('omit_vary_cookie')) {
|
||||
$response->setVary('Cookie', FALSE);
|
||||
}
|
||||
|
||||
if ($page_compression) {
|
||||
$response->setVary('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
|
||||
if ($response->headers->get('Content-Encoding') == 'gzip' && extension_loaded('zlib')) {
|
||||
if (strpos($request->headers->get('Accept-Encoding'), 'gzip') !== FALSE) {
|
||||
// The response content is already gzip'ed, so make sure
|
||||
// zlib.output_compression does not compress it once more.
|
||||
ini_set('zlib.output_compression', '0');
|
||||
$response->headers->set('content-encoding', 'gzip');
|
||||
}
|
||||
else {
|
||||
// The client does not support compression, so unzip the data in the
|
||||
// cache. Strip the gzip header and run uncompress.
|
||||
$cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8));
|
||||
// The client does not support compression. Decompress the content and
|
||||
// remove the Content-Encoding header.
|
||||
$content = $response->getContent();
|
||||
$content = gzinflate(substr(substr($content, 10), 0, -8));
|
||||
$response->setContent($content);
|
||||
$response->headers->remove('Content-Encoding');
|
||||
}
|
||||
}
|
||||
|
||||
$response->setContent($cache->data['body']);
|
||||
// Perform HTTP revalidation.
|
||||
// @todo Use Response::isNotModified() as per https://drupal.org/node/2259489
|
||||
$last_modified = $response->getLastModified();
|
||||
if ($last_modified) {
|
||||
// See if the client has provided the required HTTP headers.
|
||||
$if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
|
||||
$if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
|
||||
|
||||
if ($if_modified_since && $if_none_match
|
||||
&& $if_none_match == $response->getEtag() // etag must match
|
||||
&& $if_modified_since == $last_modified->getTimestamp()) { // if-modified-since must match
|
||||
$response->setStatusCode(304);
|
||||
$response->setContent(NULL);
|
||||
|
||||
// In the case of a 304 response, certain headers must be sent, and the
|
||||
// remaining may not (see RFC 2616, section 10.3.5).
|
||||
foreach (array_keys($response->headers->all()) as $name) {
|
||||
if (!in_array($name, array('content-location', 'expires', 'cache-control', 'vary'))) {
|
||||
$response->headers->remove($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1617,8 +1584,6 @@ function _drupal_bootstrap_kernel() {
|
|||
* Attempts to serve a page from the cache.
|
||||
*/
|
||||
function _drupal_bootstrap_page_cache() {
|
||||
global $user;
|
||||
|
||||
require_once __DIR__ . '/database.inc';
|
||||
// Check for a cache mode force from settings.php.
|
||||
if (Settings::get('page_cache_without_database')) {
|
||||
|
@ -1629,30 +1594,23 @@ function _drupal_bootstrap_page_cache() {
|
|||
$cache_enabled = $config->get('cache.page.use_internal');
|
||||
}
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$request = \Drupal::request();
|
||||
// If there is no session cookie and cache is enabled (or forced), try
|
||||
// to serve a cached page.
|
||||
if (!$request->cookies->has(session_name()) && $cache_enabled) {
|
||||
// Make sure there is a user object because its timestamp will be checked.
|
||||
$user = new AnonymousUserSession();
|
||||
if (!$request->cookies->has(session_name()) && $cache_enabled && drupal_page_is_cacheable()) {
|
||||
// Get the page from the cache.
|
||||
$cache = drupal_page_get_cache($request);
|
||||
$response = drupal_page_get_cache($request);
|
||||
// If there is a cached page, display it.
|
||||
if (is_object($cache)) {
|
||||
$response = new Response();
|
||||
if ($response) {
|
||||
$response->headers->set('X-Drupal-Cache', 'HIT');
|
||||
date_default_timezone_set(drupal_get_user_timezone());
|
||||
|
||||
drupal_serve_page_from_cache($cache, $response, $request);
|
||||
drupal_serve_page_from_cache($response, $request);
|
||||
|
||||
// We are done.
|
||||
$response->prepare($request);
|
||||
$response->send();
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
drupal_add_http_header('X-Drupal-Cache', 'MISS');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2750,55 +2750,37 @@ function _drupal_bootstrap_full($skip = FALSE) {
|
|||
* Page compression requires the PHP zlib extension
|
||||
* (http://php.net/manual/ref.zlib.php).
|
||||
*
|
||||
* @param $body
|
||||
* The response body.
|
||||
* @return
|
||||
* The cached object or NULL if the page cache was not set.
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* The fully populated response.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request for this page.
|
||||
*
|
||||
* @see drupal_page_header()
|
||||
*/
|
||||
function drupal_page_set_cache(Response $response, Request $request) {
|
||||
if (drupal_page_is_cacheable()) {
|
||||
// Check if the current page may be compressed.
|
||||
if (\Drupal::config('system.performance')->get('response.gzip') &&
|
||||
!$response->headers->get('Content-Encoding') && extension_loaded('zlib')) {
|
||||
|
||||
// Check if the current page may be compressed.
|
||||
$page_compressed = \Drupal::config('system.performance')->get('response.gzip') && extension_loaded('zlib');
|
||||
|
||||
$cache = (object) array(
|
||||
'cid' => drupal_page_cache_get_cid($request),
|
||||
'data' => array(
|
||||
'body' => $response->getContent(),
|
||||
'headers' => array(),
|
||||
// We need to store whether page was compressed or not,
|
||||
// because by the time it is read, the configuration might change.
|
||||
'page_compressed' => $page_compressed,
|
||||
),
|
||||
'tags' => HtmlViewSubscriber::convertHeaderToCacheTags($response->headers->get('X-Drupal-Cache-Tags')),
|
||||
'expire' => Cache::PERMANENT,
|
||||
'created' => REQUEST_TIME,
|
||||
);
|
||||
|
||||
$cache->data['headers'] = $response->headers->all();
|
||||
|
||||
// Hack: exclude the x-drupal-cache header; it may make it in here because
|
||||
// of awkwardness in how we defer sending it over in _drupal_page_get_cache.
|
||||
if (isset($cache->data['headers']['x-drupal-cache'])) {
|
||||
unset($cache->data['headers']['x-drupal-cache']);
|
||||
$content = $response->getContent();
|
||||
if ($content) {
|
||||
$response->setContent(gzencode($content, 9, FORCE_GZIP));
|
||||
$response->headers->set('Content-Encoding', 'gzip');
|
||||
}
|
||||
|
||||
// Use the actual timestamp from an Expires header, if available.
|
||||
if ($date = $response->getExpires()) {
|
||||
$date = DrupalDateTime::createFromDateTime($date);
|
||||
$cache->expire = $date->getTimestamp();
|
||||
}
|
||||
|
||||
if ($cache->data['body']) {
|
||||
if ($page_compressed) {
|
||||
$cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
|
||||
}
|
||||
\Drupal::cache('render')->set($cache->cid, $cache->data, $cache->expire, $cache->tags);
|
||||
}
|
||||
return $cache;
|
||||
// When page compression is enabled, ensure that proxy caches will record
|
||||
// and deliver different versions of a page depending on whether the
|
||||
// client supports gzip or not.
|
||||
$response->setVary('Accept-Encoding', FALSE);
|
||||
}
|
||||
|
||||
// Use the actual timestamp from an Expires header, if available.
|
||||
$date = $response->getExpires();
|
||||
$expire = ($date > (new DateTime())) ? $date->getTimestamp() : Cache::PERMANENT;
|
||||
|
||||
$cid = drupal_page_cache_get_cid($request);
|
||||
$tags = HtmlViewSubscriber::convertHeaderToCacheTags($response->headers->get('X-Drupal-Cache-Tags'));
|
||||
\Drupal::cache('render')->set($cid, $response, $expire, $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,8 +7,13 @@
|
|||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\ConfigFactory;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageManager;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
@ -26,14 +31,24 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* A config object for the system performance configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructs a FinishResponseSubscriber object.
|
||||
*
|
||||
* @param LanguageManager $language_manager
|
||||
* The LanguageManager object for retrieving the correct language code.
|
||||
* @param \Drupal\Core\Config\ConfigFactory $config_factory
|
||||
* A config factory for retrieving required config objects.
|
||||
*/
|
||||
public function __construct(LanguageManager $language_manager) {
|
||||
public function __construct(LanguageManager $language_manager, ConfigFactory $config_factory) {
|
||||
$this->languageManager = $language_manager;
|
||||
$this->config = $config_factory->get('system.performance');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,57 +72,148 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
// Set the Content-language header.
|
||||
$response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->id);
|
||||
|
||||
// Because pages are highly dynamic, set the last-modified time to now
|
||||
// since the page is in fact being regenerated right now.
|
||||
// @todo Remove this and use a more intelligent default so that HTTP
|
||||
// caching can function properly.
|
||||
$response->setLastModified(new \DateTime(gmdate(DATE_RFC1123, REQUEST_TIME)));
|
||||
|
||||
// Also give each page a unique ETag. This will force clients to include
|
||||
// both an If-Modified-Since header and an If-None-Match header when doing
|
||||
// conditional requests for the page (required by RFC 2616, section 13.3.4),
|
||||
// making the validation more robust. This is a workaround for a bug in
|
||||
// Mozilla Firefox that is triggered when Drupal's caching is enabled and
|
||||
// the user accesses Drupal via an HTTP proxy (see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an
|
||||
// authenticated user requests a page, and then logs out and requests the
|
||||
// same page again, Firefox may send a conditional request based on the
|
||||
// page that was cached locally when the user was logged in. If this page
|
||||
// did not have an ETag header, the request only contains an
|
||||
// If-Modified-Since header. The date will be recent, because with
|
||||
// authenticated users the Last-Modified header always refers to the time
|
||||
// of the request. If the user accesses Drupal via a proxy server, and the
|
||||
// proxy already has a cached copy of the anonymous page with an older
|
||||
// Last-Modified date, the proxy may respond with 304 Not Modified, making
|
||||
// the client think that the anonymous and authenticated pageviews are
|
||||
// identical.
|
||||
// @todo Remove this line as no longer necessary per
|
||||
// http://drupal.org/node/1573064
|
||||
$response->setEtag(REQUEST_TIME);
|
||||
|
||||
// Authenticated users are always given a 'no-cache' header, and will fetch
|
||||
// a fresh page on every request. This prevents authenticated users from
|
||||
// seeing locally cached pages.
|
||||
// @todo Revisit whether or not this is still appropriate now that the
|
||||
// Response object does its own cache control processing and we intend to
|
||||
// use partial page caching more extensively.
|
||||
|
||||
// Attach globally-declared headers to the response object so that Symfony
|
||||
// can send them for us correctly.
|
||||
// @todo remove this once we have removed all drupal_add_http_header() calls
|
||||
// @todo remove this once we have removed all drupal_add_http_header()
|
||||
// calls.
|
||||
$headers = drupal_get_http_header();
|
||||
foreach ($headers as $name => $value) {
|
||||
$response->headers->set($name, $value, FALSE);
|
||||
}
|
||||
|
||||
$max_age = \Drupal::config('system.performance')->get('cache.page.max_age');
|
||||
if ($max_age > 0 && ($cache = drupal_page_set_cache($response, $request))) {
|
||||
drupal_serve_page_from_cache($cache, $response, $request);
|
||||
$is_cacheable = drupal_page_is_cacheable();
|
||||
|
||||
// Add headers necessary to specify whether the response should be cached by
|
||||
// proxies and/or the browser.
|
||||
if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) {
|
||||
if (!$this->isCacheControlCustomized($response)) {
|
||||
$this->setResponseCacheable($response, $request);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
|
||||
$response->headers->set('Cache-Control', 'no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
$this->setResponseNotCacheable($response, $request);
|
||||
}
|
||||
|
||||
// Store the response in the internal page cache.
|
||||
if ($is_cacheable && $this->config->get('cache.page.use_internal')) {
|
||||
drupal_page_set_cache($response, $request);
|
||||
$response->headers->set('X-Drupal-Cache', 'MISS');
|
||||
drupal_serve_page_from_cache($response, $request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given response has a custom Cache-Control header.
|
||||
*
|
||||
* Upon construction, the ResponseHeaderBag is initialized with an empty
|
||||
* Cache-Control header. Consequently it is not possible to check whether the
|
||||
* header was set explicitly by simply checking its presence. Instead, it is
|
||||
* necessary to examine the computed Cache-Control header and compare with
|
||||
* values known to be present only when Cache-Control was never set
|
||||
* explicitly.
|
||||
*
|
||||
* When neither Cache-Control nor any of the ETag, Last-Modified, Expires
|
||||
* headers are set on the response, ::get('Cache-Control') returns the value
|
||||
* 'no-cache'. If any of ETag, Last-Modified or Expires are set but not
|
||||
* Cache-Control, then 'private, must-revalidate' (in exactly this order) is
|
||||
* returned.
|
||||
*
|
||||
* @see \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue()
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when Cache-Control header was set explicitely on the given response.
|
||||
*/
|
||||
protected function isCacheControlCustomized(Response $response) {
|
||||
$cache_control = $response->headers->get('Cache-Control');
|
||||
return $cache_control != 'no-cache' && $cache_control != 'private, must-revalidate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Cache-Control and Expires headers to a response which is not cacheable.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* A response object.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object.
|
||||
*/
|
||||
protected function setResponseNotCacheable(Response $response, Request $request) {
|
||||
$this->setCacheControlNoCache($response);
|
||||
$this->setExpiresNoCache($response);
|
||||
|
||||
// There is no point in sending along headers necessary for cache
|
||||
// revalidation, if caching by proxies and browsers is denied in the first
|
||||
// place. Therefore remove ETag, Last-Modified and Vary in that case.
|
||||
$response->setEtag(NULL);
|
||||
$response->setLastModified(NULL);
|
||||
$response->setVary(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Cache-Control and Expires headers to a cacheable response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* A response object.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object.
|
||||
*/
|
||||
protected function setResponseCacheable(Response $response, Request $request) {
|
||||
// HTTP/1.0 proxies do not support the Vary header, so prevent any caching
|
||||
// by sending an Expires date in the past. HTTP/1.1 clients ignore the
|
||||
// Expires header if a Cache-Control: max-age directive is specified (see
|
||||
// RFC 2616, section 14.9.3).
|
||||
if (!$response->headers->has('Expires')) {
|
||||
$this->setExpiresNoCache($response);
|
||||
}
|
||||
|
||||
$max_age = $this->config->get('cache.page.max_age');
|
||||
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
|
||||
|
||||
// In order to support HTTP cache-revalidation, ensure that there is a
|
||||
// Last-Modified and an ETag header on the response.
|
||||
if (!$response->headers->has('Last-Modified')) {
|
||||
$timestamp = REQUEST_TIME;
|
||||
$response->setLastModified(new \DateTime(gmdate(DATE_RFC1123, REQUEST_TIME)));
|
||||
}
|
||||
else {
|
||||
$timestamp = $response->getLastModified()->getTimestamp();
|
||||
}
|
||||
$response->setEtag($timestamp);
|
||||
|
||||
// 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 (!$response->hasVary() && !Settings::get('omit_vary_cookie')) {
|
||||
$response->setVary('Cookie', FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable caching in the browser and for HTTP/1.1 proxies and clients.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* A response object.
|
||||
*/
|
||||
protected function setCacheControlNoCache(Response $response) {
|
||||
$response->headers->set('Cache-Control', 'no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable caching in ancient browsers and for HTTP/1.0 proxies and clients.
|
||||
*
|
||||
* HTTP/1.0 proxies do not support the Vary header, so prevent any caching by
|
||||
* sending an Expires date in the past. HTTP/1.1 clients ignore the Expires
|
||||
* header if a Cache-Control: max-age= directive is specified (see RFC 2616,
|
||||
* section 14.9.3).
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* A response object.
|
||||
*/
|
||||
protected function setExpiresNoCache(Response $response) {
|
||||
$response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,7 +222,7 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
|
|||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::RESPONSE][] = array('onRespond');
|
||||
return $events;
|
||||
}
|
||||
|
|
|
@ -196,15 +196,31 @@ class PageCacheTest extends WebTestBase {
|
|||
$this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', 'Cache-Control header was sent.');
|
||||
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
|
||||
$this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the omit_vary_cookie setting.
|
||||
*/
|
||||
public function testPageCacheWithoutVaryCookie() {
|
||||
$config = \Drupal::config('system.performance');
|
||||
$config->set('cache.page.use_internal', 1);
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
|
||||
// Check the omit_vary_cookie setting.
|
||||
$this->drupalLogout();
|
||||
$settings['settings']['omit_vary_cookie'] = (object) array(
|
||||
'value' => TRUE,
|
||||
'required' => TRUE,
|
||||
);
|
||||
$this->writeSettings($settings);
|
||||
$this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
|
||||
|
||||
// Fill the cache.
|
||||
$this->drupalGet('');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
|
||||
$this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
|
||||
|
||||
// Check cache.
|
||||
$this->drupalGet('');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
|
||||
$this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
|
||||
}
|
||||
|
||||
|
|
|
@ -121,13 +121,11 @@ function _toolbar_initialize_page_cache() {
|
|||
// If we have a cache, serve it.
|
||||
// @see _drupal_bootstrap_page_cache()
|
||||
$request = \Drupal::request();
|
||||
$cache = drupal_page_get_cache($request);
|
||||
if (is_object($cache)) {
|
||||
$response = new Response();
|
||||
$response = drupal_page_get_cache($request);
|
||||
if ($response) {
|
||||
$response->headers->set('X-Drupal-Cache', 'HIT');
|
||||
date_default_timezone_set(drupal_get_user_timezone());
|
||||
|
||||
drupal_serve_page_from_cache($cache, $response, $request);
|
||||
drupal_serve_page_from_cache($response, $request);
|
||||
|
||||
$response->prepare($request);
|
||||
$response->send();
|
||||
|
@ -135,9 +133,6 @@ function _toolbar_initialize_page_cache() {
|
|||
exit;
|
||||
}
|
||||
|
||||
// Otherwise, create a new page response (that will be cached).
|
||||
drupal_add_http_header('X-Drupal-Cache', 'MISS');
|
||||
|
||||
// The Expires HTTP header is the heart of the client-side HTTP caching. The
|
||||
// additional server-side page cache only takes effect when the client
|
||||
// accesses the callback URL again (e.g., after clearing the browser cache or
|
||||
|
|
Loading…
Reference in New Issue