diff --git a/core/modules/page_cache/src/StackMiddleware/PageCache.php b/core/modules/page_cache/src/StackMiddleware/PageCache.php index 14f9df5..e2c6c47 100644 --- a/core/modules/page_cache/src/StackMiddleware/PageCache.php +++ b/core/modules/page_cache/src/StackMiddleware/PageCache.php @@ -7,6 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PageCache\ResponsePolicyInterface; +use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -248,10 +249,30 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch // - Get the time expiration from the Expires header, rather than the // interface, but see https://www.drupal.org/node/2352009 about possibly // changing that. - $tags = $response->getCacheableMetadata()->getCacheTags(); - $date = $response->getExpires()->getTimestamp(); - $expire = ($date > time()) ? $date : Cache::PERMANENT; - $this->set($request, $response, $expire, $tags); + $expire = FALSE; + // 404 responses can fill non-LRU cache backends and generally are likely to + // have a low cache hit rate. So do not cache them permanently. + if ($response->getStatusCode() === $response::HTTP_NOT_FOUND) { + // Cache for an hour by default, or the max age, whichever is the smaller. + // If the ttl is set to FALSE then do not cache it. + $settings_ttl = Settings::get('404_cache_ttl', 3600); + if ($settings_ttl) { + $max_age = $response->getMaxAge(); + // The response's max_age will be negative in the case the response has + // no time set. + $ttl = ($max_age > 0) ? min($max_age, $settings_ttl) : $settings_ttl; + $expire = REQUEST_TIME + $ttl; + } + } + else { + $date = $response->getExpires()->getTimestamp(); + $expire = ($date > time()) ? $date : Cache::PERMANENT; + } + + if ($expire !== FALSE) { + $tags = $response->getCacheableMetadata()->getCacheTags(); + $this->set($request, $response, $expire, $tags); + } // Mark response as a cache miss. $response->headers->set('X-Drupal-Cache', 'MISS'); diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php index 44c94e1..ee335a3 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTest.php @@ -375,6 +375,21 @@ function testPageCacheAnonymous403404() { $this->assertResponse($code); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); } + + // Disable 404 caching + $settings['settings']['404_cache_ttl'] = (object) array( + 'value' => FALSE, + 'required' => TRUE, + ); + $this->writeSettings($settings); + \Drupal::service('cache.render')->deleteAll(); + + // Getting the 404 page twice you still result in a cache miss. + $this->drupalGet($invalid_url); + $this->drupalGet($invalid_url); + $this->assertResponse(404); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + } /** diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 7f28c29..3dde82e 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -420,6 +420,18 @@ */ # $settings['omit_vary_cookie'] = TRUE; + +/** + * Cache TTL for 404 responses. + * + * Items cached per-URL tend to result in a large number of cache items, and + * this can be problematic on 404 pages which by their nature are unbounded. A + * fixed TTL can be set for these items, defaulting to one hour, so that cache + * backends which do not support LRU can purge older entries. + */ +# $settings['404_cache_ttl'] = 3600; + + /** * Class Loader. *