Skip to content

Instantly share code, notes, and snippets.

@malcolmp
Last active July 16, 2018 22:41
Show Gist options
  • Select an option

  • Save malcolmp/2bc070211c793041e040702ea10cc9c3 to your computer and use it in GitHub Desktop.

Select an option

Save malcolmp/2bc070211c793041e040702ea10cc9c3 to your computer and use it in GitHub Desktop.
A Response subscriber that bubbles up the response cache metadata to the page cache.
<?php
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\PageCache\ResponsePolicyInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to handle bubbling of response cacheable metadata to
* the max-age Cache-Control header.
*
* @see Drupal\Core\EventSubscriber\FinishResponseSubscriber
* @see https://www.drupal.org/node/2352009
* @see https://www.drupal.org/node/2732129
*/
class MaxAgeBubblingResponseSubscriber implements EventSubscriberInterface {
/**
* A config object for the system performance configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $performanceConfig;
protected $customConfig;
/**
* A policy rule determining the cacheability of a request.
*
* @var \Drupal\Core\PageCache\RequestPolicyInterface
*/
protected $requestPolicy;
/**
* A policy rule determining the cacheability of the response.
*
* @var \Drupal\Core\PageCache\ResponsePolicyInterface
*/
protected $responsePolicy;
/**
* Constructs an EventSubscriber object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A config factory for retrieving required config objects.
* @param RequestPolicyInterface $request_policy
* @param ResponsePolicyInterface $response_policy
*/
public function __construct(ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy) {
$this->performanceConfig = $config_factory->get('system.performance');
$this->customConfig = $config_factory->get('my_custom_performance.settings');
$this->requestPolicy = $request_policy;
$this->responsePolicy = $response_policy;
}
/**
* 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() || !$this->isCacheable($event)) {
return;
}
$bubble_max_age = $this->customConfig->get('cache.page.bubble_max_age', TRUE);
if ($bubble_max_age == FALSE) { return; }
$response = $event->getResponse();
$max_age = $this->performanceConfig->get('cache.page.max_age');
if ($max_age > 0 && $response instanceof CacheableResponseInterface) {
$cacheMetadata = $response->getCacheableMetadata();
$cacheMaxAge = $cacheMetadata->getCacheMaxAge();
if ($cacheMaxAge > 0) {
$max_age = min($max_age, $cacheMaxAge);
} else if ($cacheMaxAge === 0 && $this->customConfig->get('cache.page.allow_uncacheable')) {
// Only allow uncacheable requests if setting is TRUE.
$max_age = 0;
}
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
}
}
protected function isCacheable(FilterResponseEvent $event) {
$request = $event->getRequest();
$response = $event->getResponse();
// TODO technically, we may be ok in some cases only checking responsePolicy
// but the NoSessionOpen check is request only so for now authenticated responses
// can not be cached for anonymous, even if it was ok to do so.
return ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Our EventSubscriber must be after Drupal\Core\EventSubscriber\FinishResponseSubscriber
$events[KernelEvents::RESPONSE][] = ['onRespond', -1];
return $events;
}
}
services:
my_custom.max_age_response_subscriber:
class: Drupal\my_custom\EventSubscriber\MaxAgeBubblingResponseSubscriber
arguments: ['@config.factory', '@page_cache_request_policy', '@page_cache_response_policy']
tags:
- { name: event_subscriber }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment