Skip to content

Instantly share code, notes, and snippets.

@Berdir
Last active July 21, 2021 23:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Berdir/3fe65aa3b7ad86e16a5e to your computer and use it in GitHub Desktop.
Save Berdir/3fe65aa3b7ad86e16a5e to your computer and use it in GitHub Desktop.
Varnish cache tag purger
sub vcl_recv {
if (req.request == "BAN") {
if (!client.ip ~ internal) {
error 405 "Method not allowed";
}
ban("obj.http.X-Drupal-Cache-Tags ~ " + req.http.X-Drupal-Cache-Tag-Clears);
error 200 "Banned" ;
}
}
<?php
/**
* @file
* Contains \Drupal\varnish_cache_tag_purger\TagDeletionListener.
*/
namespace Drupal\varnish_cache_tag_purger;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\Site\Settings;
use GuzzleHttp\ClientInterface;
/**
* A fake cache backend that sends purge requests to varnish.
*/
class TagDeletionListener extends NullBackend {
/**
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $http_client) {
parent::__construct('fake');
$this->httpClient = $http_client;
}
/**
* {@inheritdoc}
*/
public function deleteTags(array $tags) {
$this->purgeTags($tags);
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
$this->purgeTags($tags);
}
/**
* Purges caches with those tags from Varnish.
*
* @param array $tags
* Array of cache tags.
*/
protected function purgeTags(array $tags) {
if ($varnish_url = Settings::get('varnish_url')) {
$request = $this->httpClient->createRequest('BAN', $varnish_url)
->addHeader('X-Drupal-Cache-Tag-Clears', $this->convertCacheTagsToClearRegex($tags));
$this->httpClient->send($request);
}
}
/**
* Converts the cache tag array structure to a varnish compatible regex.
*
* @param array $tags
* Cache tags.
*
* @return string
* String in the form of "(\Dnode:1\D|\Dnodes\D)", taken from
*
* @see http://www.smashingmagazine.com/2014/04/23/cache-invalidation-strategies-with-varnish-cache/
*/
public function convertCacheTagsToClearRegex(array $tags) {
$flat_tags = array();
foreach ($tags as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$flat_tags[] = "$namespace:$value";
}
}
else {
$flat_tags[] = "$namespace:$values";
}
}
return '(\D' . implode('\D|\D', $flat_tags) . '\D)';
}
}
services:
# Define a fake cache bin by tagging it with cache.bin. This only implements
# methods to clear cache tags.
varnish_cache_tag_purger.tag_deletion_listener:
class: Drupal\varnish_cache_tag_purger\TagDeletionListener
arguments: ['@http_client']
tags:
- { name: cache.bin }
@nielsvm
Copy link

nielsvm commented Aug 8, 2014

This is very, very nice and proves what we were all thinking already. Thanks Berdir!

So, as I'm working today (and next week on this), I'll translate and incorporate this into a VarnishPocPurger that'll do exactly what you are doing here. I was thinking of taking the same approach as you did by overloading the page cache, which will become a second (lightweight) module called 'purge_cachetags'. The module will be part of the purge project but allowing a separation of (API) concerns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment