Example #1
0
 /**
  * Fetches a response from the backend and stores it 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).
  *
  * @see drupal_page_header()
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   A request object.
  * @param int $type
  *   The type of the request (one of HttpKernelInterface::MASTER_REQUEST or
  *   HttpKernelInterface::SUB_REQUEST)
  * @param bool $catch
  *   Whether to catch exceptions or not
  *
  * @returns \Symfony\Component\HttpFoundation\Response $response
  *   A response object.
  */
 protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE)
 {
     $response = $this->httpKernel->handle($request, $type, $catch);
     // 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 $response;
     }
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
         return $response;
     }
     // Check if the current page may be compressed.
     if (extension_loaded('zlib') && !$response->headers->get('Content-Encoding') && $this->config('system.performance')->get('response.gzip')) {
         $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;
     $tags = explode(' ', $response->headers->get('X-Drupal-Cache-Tags'));
     $this->set($request, $response, $expire, $tags);
     // Mark response as a cache miss.
     $response->headers->set('X-Drupal-Cache', 'MISS');
     return $response;
 }
 /**
  * Sets extra headers on successful responses.
  *
  * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  *   The event to process.
  */
 public function onRespond(FilterResponseEvent $event)
 {
     if (!$event->isMasterRequest()) {
         return;
     }
     $request = $event->getRequest();
     $response = $event->getResponse();
     // Set the X-UA-Compatible HTTP header to force IE to use the most recent
     // rendering engine.
     $response->headers->set('X-UA-Compatible', 'IE=edge', FALSE);
     // Set the Content-language header.
     $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());
     // Prevent browsers from sniffing a response and picking a MIME type
     // different from the declared content-type, since that can lead to
     // XSS and other vulnerabilities.
     // https://www.owasp.org/index.php/List_of_useful_HTTP_headers
     $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
     $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE);
     // If the current response isn't an implementation of the
     // CacheableResponseInterface, we assume that a Response is either
     // explicitly not cacheable or that caching headers are already set in
     // another place.
     if (!$response instanceof CacheableResponseInterface) {
         if (!$this->isCacheControlCustomized($response)) {
             $this->setResponseNotCacheable($response, $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);
         }
         return;
     }
     if ($this->debugCacheabilityHeaders) {
         // Expose the cache contexts and cache tags associated with this page in a
         // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively.
         $response_cacheability = $response->getCacheableMetadata();
         $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
         $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts())));
     }
     $is_cacheable = $this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW && $this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY;
     // 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)) {
             // Only add the default Cache-Control header if the controller did not
             // specify one on the response.
             $this->setResponseCacheable($response, $request);
         }
     } else {
         // If either the policy forbids caching or the sites configuration does
         // not allow to add a max-age directive, then enforce a Cache-Control
         // header declaring the response as not cacheable.
         $this->setResponseNotCacheable($response, $request);
     }
 }
Example #3
0
 /**
  * Sets extra headers on successful responses.
  *
  * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  *   The event to process.
  */
 public function onRespond(FilterResponseEvent $event)
 {
     if (!$event->isMasterRequest()) {
         return;
     }
     $request = $event->getRequest();
     $response = $event->getResponse();
     // Set the X-UA-Compatible HTTP header to force IE to use the most recent
     // rendering engine.
     $response->headers->set('X-UA-Compatible', 'IE=edge', FALSE);
     // Set the Content-language header.
     $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());
     // Prevent browsers from sniffing a response and picking a MIME type
     // different from the declared content-type, since that can lead to
     // XSS and other vulnerabilities.
     // https://www.owasp.org/index.php/List_of_useful_HTTP_headers
     $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
     // Attach globally-declared headers to the response object so that Symfony
     // can send them for us correctly.
     // @todo Remove this once drupal_process_attached() no longer calls
     //    _drupal_add_http_header(), which has its own static. Instead,
     //    _drupal_process_attached() should use
     //    \Symfony\Component\HttpFoundation\Response->headers->set(), which is
     //    already documented on the (deprecated) _drupal_process_attached() to
     //    become the final, intended mechanism.
     $headers = drupal_get_http_header();
     foreach ($headers as $name => $value) {
         // Symfony special-cases the 'Status' header.
         if ($name === 'status') {
             $response->setStatusCode($value);
         }
         $response->headers->set($name, $value, FALSE);
     }
     // Expose the cache contexts and cache tags associated with this page in a
     // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively.
     if ($response instanceof CacheableResponseInterface) {
         $response_cacheability = $response->getCacheableMetadata();
         $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
         $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts())));
     }
     $is_cacheable = $this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW && $this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY;
     // 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)) {
             // Only add the default Cache-Control header if the controller did not
             // specify one on the response.
             $this->setResponseCacheable($response, $request);
         }
     } else {
         // If either the policy forbids caching or the sites configuration does
         // not allow to add a max-age directive, then enforce a Cache-Control
         // header declaring the response as not cacheable.
         $this->setResponseNotCacheable($response, $request);
     }
 }
 /**
  * Sets extra headers on successful responses.
  *
  * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  *   The event to process.
  */
 public function onRespond(FilterResponseEvent $event)
 {
     if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
         return;
     }
     $request = $event->getRequest();
     $response = $event->getResponse();
     // Set the X-UA-Compatible HTTP header to force IE to use the most recent
     // rendering engine or use Chrome's frame rendering engine if available.
     $response->headers->set('X-UA-Compatible', 'IE=edge,chrome=1', FALSE);
     // Set the Content-language header.
     $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());
     // Attach globally-declared headers to the response object so that Symfony
     // can send them for us correctly.
     // @todo Remove this once drupal_process_attached() no longer calls
     //    _drupal_add_http_header(), which has its own static. Instead,
     //    _drupal_process_attached() should use
     //    \Symfony\Component\HttpFoundation\Response->headers->set(), which is
     //    already documented on the (deprecated) _drupal_process_attached() to
     //    become the final, intended mechanism.
     $headers = drupal_get_http_header();
     foreach ($headers as $name => $value) {
         // Symfony special-cases the 'Status' header.
         if ($name === 'status') {
             $response->setStatusCode($value);
         }
         $response->headers->set($name, $value, FALSE);
     }
     $is_cacheable = $this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW && $this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY;
     // 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)) {
             // Only add the default Cache-Control header if the controller did not
             // specify one on the response.
             $this->setResponseCacheable($response, $request);
         }
     } else {
         // If either the policy forbids caching or the sites configuration does
         // not allow to add a max-age directive, then enforce a Cache-Control
         // 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);
     }
 }
Example #5
0
 /**
  * Fetches a response from the backend and stores it 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).
  *
  * @see drupal_page_header()
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   A request object.
  * @param int $type
  *   The type of the request (one of HttpKernelInterface::MASTER_REQUEST or
  *   HttpKernelInterface::SUB_REQUEST)
  * @param bool $catch
  *   Whether to catch exceptions or not
  *
  * @returns \Symfony\Component\HttpFoundation\Response $response
  *   A response object.
  */
 protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE)
 {
     /** @var \Symfony\Component\HttpFoundation\Response $response */
     $response = $this->httpKernel->handle($request, $type, $catch);
     // Drupal's primary cache invalidation architecture is cache tags: any
     // response that varies by a configuration value or data in a content
     // entity should have cache tags, to allow for instant cache invalidation
     // when that data is updated. However, HTTP does not standardize how to
     // encode cache tags in a response. Different CDNs implement their own
     // approaches, and configurable reverse proxies (e.g., Varnish) allow for
     // custom implementations. To keep Drupal's internal page cache simple, we
     // only cache CacheableResponseInterface responses, since those provide a
     // defined API for retrieving cache tags. For responses that do not
     // implement CacheableResponseInterface, there's no easy way to distinguish
     // responses that truly don't depend on any site data from responses that
     // contain invalidation information customized to a particular proxy or
     // CDN.
     // - Drupal modules are encouraged to use CacheableResponseInterface
     //   responses where possible and to leave the encoding of that information
     //   into response headers to the corresponding proxy/CDN integration
     //   modules.
     // - Custom applications that wish to provide internal page cache support
     //   for responses that do not implement CacheableResponseInterface may do
     //   so by replacing/extending this middleware service or adding another
     //   one.
     if (!$response instanceof CacheableResponseInterface) {
         return $response;
     }
     // Currently it is not possible to cache binary file or streamed responses:
     // https://github.com/symfony/symfony/issues/9128#issuecomment-25088678.
     // Therefore exclude them, even for subclasses that implement
     // CacheableResponseInterface.
     if ($response instanceof BinaryFileResponse || $response instanceof StreamedResponse) {
         return $response;
     }
     // Allow policy rules to further restrict which responses to cache.
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
         return $response;
     }
     // The response passes all of the above checks, so cache it.
     // - Get the tags from CacheableResponseInterface per the earlier comments.
     // - 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);
     // Mark response as a cache miss.
     $response->headers->set('X-Drupal-Cache', 'MISS');
     return $response;
 }
 /**
  * Stores a response in case of a Dynamic Page Cache miss, if cacheable.
  *
  * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  *   The event to process.
  */
 public function onResponse(FilterResponseEvent $event)
 {
     $response = $event->getResponse();
     // Dynamic Page Cache only works with cacheable responses. It does not work
     // with plain Response objects. (Dynamic Page Cache needs to be able to
     // access and modify the cacheability metadata associated with the
     // response.)
     if (!$response instanceof CacheableResponseInterface) {
         return;
     }
     // There's no work left to be done if this is a Dynamic Page Cache hit.
     if ($response->headers->get(self::HEADER) === 'HIT') {
         return;
     }
     // There's no work left to be done if this is an uncacheable response.
     if (!$this->shouldCacheResponse($response)) {
         // The response is uncacheable, mark it as such.
         $response->headers->set(self::HEADER, 'UNCACHEABLE');
         return;
     }
     // Don't cache the response if Dynamic Page Cache's request subscriber did
     // not fire, because that means it is impossible to have a Dynamic Page
     // Cache hit. This can happen when the master request is for example a 403
     // or 404, in which case a subrequest is performed by the router. In that
     // case, it is the subrequest's response that is cached by Dynamic Page
     // Cache, because the routing happens in a request subscriber earlier than
     // Dynamic Page Cache's and immediately sets a response, i.e. the one
     // returned by the subrequest, and thus causes Dynamic Page Cache's request
     // subscriber to not fire for the master request.
     // @see \Drupal\Core\Routing\AccessAwareRouter::checkAccess()
     // @see \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber::on403()
     $request = $event->getRequest();
     if (!isset($this->requestPolicyResults[$request])) {
         return;
     }
     // Don't cache the response if the Dynamic Page Cache request & response
     // policies are not met.
     // @see onRouteMatch()
     if ($this->requestPolicyResults[$request] === RequestPolicyInterface::DENY || $this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
         return;
     }
     // Embed the response object in a render array so that RenderCache is able
     // to cache it, handling cache redirection for us.
     $response_as_render_array = $this->responseToRenderArray($response);
     $this->renderCache->set($response_as_render_array, $this->dynamicPageCacheRedirectRenderArray);
     // The response was generated, mark the response as a cache miss. The next
     // time, it will be a cache hit.
     $response->headers->set(self::HEADER, 'MISS');
 }
Example #7
0
 /**
  * Fetches a response from the backend and stores it 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).
  *
  * @see drupal_page_header()
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   A request object.
  * @param int $type
  *   The type of the request (one of HttpKernelInterface::MASTER_REQUEST or
  *   HttpKernelInterface::SUB_REQUEST)
  * @param bool $catch
  *   Whether to catch exceptions or not
  *
  * @returns \Symfony\Component\HttpFoundation\Response $response
  *   A response object.
  */
 protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE)
 {
     $response = $this->httpKernel->handle($request, $type, $catch);
     // 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 $response;
     }
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
         return $response;
     }
     // Use the actual timestamp from an Expires header, if available.
     $date = $response->getExpires()->getTimestamp();
     $expire = $date > time() ? $date : Cache::PERMANENT;
     $tags = explode(' ', $response->headers->get('X-Drupal-Cache-Tags'));
     $this->set($request, $response, $expire, $tags);
     // Mark response as a cache miss.
     $response->headers->set('X-Drupal-Cache', 'MISS');
     return $response;
 }