diff --git a/core/core.services.yml b/core/core.services.yml index 631b2a4..81bb939 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -112,6 +112,9 @@ services: class: Drupal\Core\PageCache\ResponsePolicy\KillSwitch tags: - { name: page_cache_response_policy } + page_cache_store: + class: Drupal\Core\PageCache\CacheStore + arguments: ['@cache.render'] config.manager: class: Drupal\Core\Config\ConfigManager arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage', '@event_dispatcher'] @@ -406,7 +409,7 @@ services: - { name: http_middleware, priority: 300 } http_middleware.page_cache: class: Drupal\Core\StackMiddleware\PageCache - arguments: ['@kernel'] + arguments: ['@page_cache_store', '@config.factory', '@page_cache_request_policy', '@page_cache_response_policy'] tags: - { name: http_middleware, priority: 200 } http_middleware.kernel_pre_handle: diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 553811f..2e98c6c 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -131,25 +131,18 @@ const DRUPAL_BOOTSTRAP_KERNEL = 1; /** - * Third bootstrap phase: try to serve a cached page. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - */ -const DRUPAL_BOOTSTRAP_PAGE_CACHE = 2; - -/** * Fourth bootstrap phase: load code for subsystems and modules. * * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. */ -const DRUPAL_BOOTSTRAP_CODE = 3; +const DRUPAL_BOOTSTRAP_CODE = 2; /** * Final bootstrap phase: initialize language, path, theme, and modules. * * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. */ -const DRUPAL_BOOTSTRAP_FULL = 4; +const DRUPAL_BOOTSTRAP_FULL = 3; /** * Role ID for anonymous users; should match what's in the "role" table. @@ -374,22 +367,6 @@ function drupal_page_cache_get_cid(Request $request) { } /** - * Retrieves the current page from the cache. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request for this page. - * - * @return \Symfony\Component\HttpFoundation\Response - * The response, if the page was found in the cache, NULL otherwise. - */ -function drupal_page_get_cache(Request $request) { - $cache = \Drupal::cache('render')->get(drupal_page_cache_get_cid($request)); - if ($cache) { - return $cache->data; - } -} - -/** * Sets an HTTP response header for the current page. * * Note: When sending a Content-Type header, always include a 'charset' type, @@ -1008,10 +985,6 @@ function drupal_bootstrap($phase = NULL) { $kernel->boot(); break; - case DRUPAL_BOOTSTRAP_PAGE_CACHE: - $kernel->handlePageCache($request); - break; - case DRUPAL_BOOTSTRAP_CODE: case DRUPAL_BOOTSTRAP_FULL: $kernel->prepareLegacyRequest($request); diff --git a/core/includes/common.inc b/core/includes/common.inc index fde8b06..2f2be27 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2278,50 +2278,6 @@ function drupal_clear_js_cache() { } /** - * Stores the current page in the cache. - * - * If page_compression is enabled, a gzipped version of the page is stored in - * the cache to avoid compressing the output on each request. The cache entry - * is unzipped in the relatively rare event that the page is requested by a - * client without gzip support. - * - * Page compression requires the PHP zlib extension - * (http://php.net/manual/ref.zlib.php). - * - * @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) { - // Check if the current page may be compressed. - if (\Drupal::config('system.performance')->get('response.gzip') && - !$response->headers->get('Content-Encoding') && extension_loaded('zlib')) { - - $content = $response->getContent(); - if ($content) { - $response->setContent(gzencode($content, 9, FORCE_GZIP)); - $response->headers->set('Content-Encoding', 'gzip'); - } - - // 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); -} - -/** * Sets the main page content value for later use. * * Given the nature of the Drupal page handling, this will be called once with diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 8514448..0e704c3 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -450,43 +450,6 @@ public function preHandle(Request $request) { /** * {@inheritdoc} - * - * @todo Invoke proper request/response/terminate events. - */ - public function handlePageCache(Request $request) { - $this->boot(); - $this->initializeCookieGlobals($request); - - // Check for a cache mode force from settings.php. - if (Settings::get('page_cache_without_database')) { - $cache_enabled = TRUE; - } - else { - $config = $this->getContainer()->get('config.factory')->get('system.performance'); - $cache_enabled = $config->get('cache.page.use_internal'); - } - - $request_policy = \Drupal::service('page_cache_request_policy'); - if ($cache_enabled && $request_policy->check($request) === RequestPolicyInterface::ALLOW) { - // Get the page from the cache. - $response = drupal_page_get_cache($request); - // If there is a cached page, display it. - if ($response) { - $response->headers->set('X-Drupal-Cache', 'HIT'); - - drupal_serve_page_from_cache($response, $request); - - // We are done. - $response->prepare($request); - $response->send(); - exit; - } - } - return $this; - } - - /** - * {@inheritdoc} */ public function discoverServiceProviders() { $this->serviceYamls = array( diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index ca7af2b..0db4405 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -86,16 +86,6 @@ public function getSitePath(); public function updateModules(array $module_list, array $module_filenames = array()); /** - * Attempts to serve a page from the cache. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The current request. - * - * @return $this - */ - public function handlePageCache(Request $request); - - /** * Prepare the kernel for handling a request without handling the request. * * @param \Symfony\Component\HttpFoundation\Request $request diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php index c5a7be1..4136285 100644 --- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php @@ -122,17 +122,6 @@ public function onRespond(FilterResponseEvent $event) { // header declaring the response as not cacheable. $this->setResponseNotCacheable($response, $request); } - - // Currently it is not possible to cache some types of responses. Therefore - // exclude binary file responses (generated files, e.g. images with image - // styles) and streamed responses (files directly read from the disk). - // see: https://github.com/symfony/symfony/issues/9128#issuecomment-25088678 - if ($is_cacheable && $this->config->get('cache.page.use_internal') && !($response instanceof BinaryFileResponse) && !($response instanceof StreamedResponse)) { - // Store the response in the internal page cache. - drupal_page_set_cache($response, $request); - $response->headers->set('X-Drupal-Cache', 'MISS'); - drupal_serve_page_from_cache($response, $request); - } } /** diff --git a/core/lib/Drupal/Core/PageCache/CacheStore.php b/core/lib/Drupal/Core/PageCache/CacheStore.php new file mode 100644 index 0000000..20bf785 --- /dev/null +++ b/core/lib/Drupal/Core/PageCache/CacheStore.php @@ -0,0 +1,123 @@ +cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function lookup(Request $request) { + $cache = $this->cache->get(drupal_page_cache_get_cid($request)); + if ($cache) { + $response = $cache->data; + $response->headers->set('X-Drupal-Cache', 'HIT'); + + return $response; + } + } + + /** + * {@inheritdoc} + */ + public function write(Request $request, Response $response) { + // Check if the current page may be compressed. + if (\Drupal::config('system.performance')->get('response.gzip') && + !$response->headers->get('Content-Encoding') && extension_loaded('zlib')) { + + $content = $response->getContent(); + if ($content) { + $response->setContent(gzencode($content, 9, FORCE_GZIP)); + $response->headers->set('Content-Encoding', 'gzip'); + } + + // 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')); + $this->cache->set($cid, $response, $expire, $tags); + + $response->headers->set('X-Drupal-Cache', 'MISS'); + } + + /** + * {@inheritdoc} + */ + public function invalidate(Request $request) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function lock(Request $request) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function unlock(Request $request) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function isLocked(Request $request) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function purge($url) { + throw new \BadMethodCallException('Not yet implemented'); + } + + /** + * {@inheritdoc} + */ + public function cleanup() { + } +} diff --git a/core/lib/Drupal/Core/StackMiddleware/PageCache.php b/core/lib/Drupal/Core/StackMiddleware/PageCache.php index ba59e93..a8c5d05 100644 --- a/core/lib/Drupal/Core/StackMiddleware/PageCache.php +++ b/core/lib/Drupal/Core/StackMiddleware/PageCache.php @@ -7,49 +7,113 @@ namespace Drupal\Core\StackMiddleware; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DrupalKernelInterface; +use Drupal\Core\PageCache\RequestPolicyInterface; +use Drupal\Core\PageCache\ResponsePolicyInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; /** * Executes the page caching before the main kernel takes over the request. */ -class PageCache implements HttpKernelInterface { +class PageCache extends HttpCache { /** - * The wrapped HTTP kernel. + * A config object for the system performance configuration. * - * @var \Symfony\Component\HttpKernel\HttpKernelInterface + * @var \Drupal\Core\Config\Config */ - protected $httpKernel; + protected $config; /** - * The main Drupal kernel. + * The request policy. * - * @var \Drupal\Core\DrupalKernelInterface + * @var \Drupal\Core\PageCache\RequestPolicyInterface */ - protected $drupalKernel; + protected $requestPolicy; /** - * Constructs a ReverseProxyMiddleware object. + * The response policy. * - * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel - * The decorated kernel. - * @param \Drupal\Core\DrupalKernelInterface $drupal_kernel - * The main Drupal kernel. + * @var \Drupal\Core\PageCache\ResponsePolicyInterface */ - public function __construct(HttpKernelInterface $http_kernel, DrupalKernelInterface $drupal_kernel) { - $this->httpKernel = $http_kernel; - $this->drupalKernel = $drupal_kernel; + protected $responsePolicy; + + /** + * Constructs a new page cache middleware. + * + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel + * An HttpKernelInterface instance. + * @param \Symfony\Component\HttpKernel\HttpCache\StoreInterface $store + * A Store instance + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * A config factory for retrieving required config objects. + * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy + * A policy rule determining the cacheability of a request. + * @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy + * A policy rule determining the cacheability of a response. + * @param \Symfony\Component\HttpKernel\HttpCache\Esi $esi + * (optional) An Esi instance + * @param array $options + * (optional) An array of options + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, Esi $esi = NULL, array $options = array()) { + parent::__construct($kernel, $store, $esi, $options); + $this->config = $config_factory->get('system.performance'); + $this->requestPolicy = $request_policy; + $this->responsePolicy = $response_policy; + } + + /** + * {@inheritdoc} + */ + protected function lookup(Request $request, $catch = FALSE) { + if ($this->config->get('cache.page.use_internal') && $this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) { + $response = parent::lookup($request, $catch); + drupal_serve_page_from_cache($response, $request); + } + else { + $response = $this->pass($request, $catch); + } + + return $response; } /** * {@inheritdoc} */ - public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { - $this->drupalKernel->handlePageCache($request); + protected function store(Request $request, Response $response) { + // Currently it is not possible to cache some types of responses. Therefore + // exclude binary file responses (generated files, e.g. images with image + // styles) and streamed responses (files directly read from the disk). + // see: https://github.com/symfony/symfony/issues/9128#issuecomment-25088678 + if ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse) { + return; + } + + if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) { + return; + } + + parent::store($request, $response); + } + + /** + * {@inheritdoc} + */ + protected function forward(Request $request, $catch = FALSE, Response $entry = NULL) { + $response = parent::forward($request, $catch, $entry); + + // Disable the file inclusion / evaluation feature of the parent class. + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Body-File'); - return $this->httpKernel->handle($request, $type, $catch); + return $response; } } diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php index 3386979..8c7d556 100644 --- a/core/modules/system/tests/http.php +++ b/core/modules/system/tests/http.php @@ -26,7 +26,6 @@ $request = Request::createFromGlobals(); $kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE); $response = $kernel - ->handlePageCache($request) ->handle($request) // Handle the response object. ->prepare($request)->send(); diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php index 00a6b13..702184d 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -28,7 +28,6 @@ $request = Request::createFromGlobals(); $kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE); $response = $kernel - ->handlePageCache($request) ->handle($request) // Handle the response object. ->prepare($request)->send();