10

We have written some custom blocks that render dynamic content; unfortunately, the caching defaults cache the first render of this block (and don't refresh for anonymous users). Cache control seems to only work for authenticated users. Are we missing something obvious? Is it possible to control anonymous caching on a block (or page) basis?

1
  • Is the block implemented in a module?
    – apaderno
    Mar 31, 2016 at 7:51

4 Answers 4

14

There are two different page caches.

The Internal Dynamic Page Cache works with all kind of elements like blocks and nodes and is uses the cache tags, cache contexts and cache max-age provided by these elements.

The Internal Page Cache is for complete pages for anonymous users and only uses cache tags.

You can control the page cache for anonymous users with one parameter.

Go to the perfomance configuration:

admin/config/development/performance

And configure the page cache maximum age with the time how long your information is valid. This is also important for proxies and the browser cache, because it would not help to invalidate the drupal cache, if the page is cached in other places. After that time the page expires and should be built again. According to the documentation:

Configuring the internal page cache On the Performance page, you can configure how long browsers and proxies may cache pages; that setting is also respected by the Internal Page Cache module. There is no other configuration.

But this is not true. You should configure this time for proxies and browser cache, but the internal page cache does not respect the time you set there. The only reliable solution I found is to uninstall the module Internal Page Cache or set an expire time in an EventSubscriber (for example 3600 sec in the future):

/src/EventSubscriber/SetExpiresSubscriber.php

<?php

namespace Drupal\mymodule\EventSubscriber;

use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class SetExpiresSubscriber implements EventSubscriberInterface {

  public function onResponse(FilterResponseEvent $event) {
    $request = $event->getRequest();
    $response = $event->getResponse();
    if ($event->isMasterRequest()) {
      $request_time = $request->server->get('REQUEST_TIME');
      $expires_time = (new \Datetime)->setTimestamp($request_time + 3600);
      $response->setExpires($expires_time);
    }
  }

  public static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE][] = ['onResponse'];
    return $events;
  }

}

mymodule.services.yml

services:
  mymodule.setexpires:
    class: Drupal\mymodule\EventSubscriber\SetExpiresSubscriber
    tags:
      - { name: event_subscriber }
6
  • 3
    You can disable the internal page cache with \Drupal::service('page_cache_kill_switch')->trigger(). Of course, if your block is displayed on all pages then that's kinda pointless.
    – Berdir
    Mar 31, 2016 at 17:32
  • Yeah, the page_kill_switch helped us out! Thank you!
    – gavz
    Mar 31, 2016 at 23:25
  • @gavz When using the switch, test with curl --head, that max-age is correct in Cache-Control:
    – 4uk4
    Apr 1, 2016 at 6:43
  • 1
    You could also use a "RequestPolicyInterface" class to deny caching according to some parameters and the request. See api.drupal.org/api/drupal/… for more information.
    – Renrhaf
    Mar 2, 2017 at 3:14
  • To let the performance page settings control the internal page cache expiration, the service's onResponse() method in this example could be updated to use $page_max_age = \Drupal::config('system.performance')->get('cache.page.max_age'); instead of hard-coding the expiration to a fixed number of seconds in the future such as 3600. Nov 17, 2017 at 18:24
4

Check out Added 'no_cache' route option to mark a route's responses as uncacheable

In your routing.yml file:

some.route:
   path: '/some/path'
   defaults:
     _controller: '\Drupal\Some\Controller::response()'
   options:
     no_cache: TRUE
1
  • Is there a way to achieve the same outcome, no_cache: TRUE for a particular path, but via theme-level preprocessing?
    – owenmck
    Jan 6 at 4:29
1

If you need the Internal Page Cache enabled (i.e., you can't use Varnish or another memory-based solution), and you simply want Internal Page Cache to respect the time set on at admin/config/development/performance, you could add your own Event Subscriber that modifies the FinishResponseSubscriber that Drupal provides. You can even use Drupal Console to generate most of the Event Subscriber for you, and then take the other bits from FinishResponseSubscriber. Your result would look something like this:

/**
 * Class MyModuleSubscriber.
 *
 * @package Drupal\my_module
 */
class MyModuleSubscriber implements EventSubscriberInterface {

  /**
   * 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();

    $this->setExpiresNoCache($response);

    return;

  }

  /**
   * 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(REQUEST_TIME + 300);
  }

  /**
   * Registers the methods in this class that should be listeners.
   *
   * @return array
   *   An array of event listener definitions.
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE][] = array('onRespond');
    return $events;

  }
}

The above hardcodes the cache lifetime at 300 seconds, but you could easily retrieve the user setting instead.

3
  • > "You can even use Drupal Console to generate most of the Event Subscriber for you," What do I need for? Modul, Controller? Where to past this codec? Mar 14, 2017 at 19:20
  • Hi @FrankGiesecke, there are a number of ways to get Drupal Console. See drupalconsole.com Mar 15, 2017 at 20:22
  • Hi, Console runs now. Which files Do I still need? Mar 19, 2017 at 13:02
0

I think you need to disable the 'Internal Page Cache' module to get cache contexts working.

With 'Internal Page Cache' enabled content for anonymous users is cached by default.

If you turn off 'Internal Page Cache' you still need to tell Drupal to not cache for anonymous users

$variables['#cache'] = [
  'contexts' => [
    // The "current user" is used above, which depends on the request,
    // so we tell Drupal to vary by the 'user' cache context.
    'user',
  ],
];

does it for me - but is probably not sompletely correct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.