Feature request: clear varnish cache if FAL file is replaced
Hi
On a project, a BE user frequently need to change a file without changing its name. This file is downloaded regularly by external application, so the url needs to be stable.
That file should be cached on browsers/clients to save bandwidth, and also in Varnish cache to unload the backend.
Currently, there is no hook allowing the user to automatically BAN the Varnish cache object associated with the file's url.
Here is a working patch on our system, that can be added to ext:varnish.
Cheers
Registering the Events in Services.yaml
services:
Site\Site\EventListener\VarnishEventListener:
tags:
- name: event.listener
identifier: 'site/varnish'
method: 'afterFileReplaced'
event: TYPO3\CMS\Core\Resource\Event\AfterFileReplacedEvent
Event listener triggered when a file is replaced in FAL
<?php
declare(strict_types=1);
namespace Site\Site\EventListener;
use Opsone\Varnish\Utility\VarnishGeneralUtility;
use Opsone\Varnish\Utility\VarnishHttpUtility;
use TYPO3\CMS\Core\Resource\Event\AfterFileReplacedEvent;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class VarnishEventListener
{
public function afterFileReplaced(AfterFileReplacedEvent $event): void
{
$file = $event->getFile();
$folder = $file->getParentFolder();
if ($folder->getStorage()->getDriverType() !== 'Local' || !$folder->getStorage()->isPublic()) {
return;
}
$storageConfiguration = $folder->getStorage()->getConfiguration();
if (empty($storageConfiguration)) {
return;
}
$url = $file->getPublicUrl();
if (empty($url)) {
return;
}
$this->clearVarnishCache($url);
}
protected function clearVarnishCache(string $url): void
{
// assign Varnish daemon hostnames
$instanceHostnames = VarnishGeneralUtility::getProperty('instanceHostnames');
if (empty($instanceHostnames)) {
$instanceHostnames = GeneralUtility::getIndpEnv('HTTP_HOST');
}
// convert Comma separated List into a Array
$instanceHostnames = GeneralUtility::trimExplode(',', $instanceHostnames, true);
// if cacheCmd is a single Page, issue BAN Command on this pid
// all other Commands ("page", "all") led to a BAN of the whole Cache
$command = [
'Varnish-Ban-TYPO3-Sitename: ' . VarnishGeneralUtility::getSitename(),
];
$method = VarnishGeneralUtility::getProperty('banRequestMethod') ?: 'BAN';
$varnishHttp = GeneralUtility::makeInstance(VarnishHttpUtility::class);
foreach ($instanceHostnames as $currentHost) {
$varnishHttp::addCommand($method, $currentHost . $url, $command);
}
}
}
Extra lines in vcl_recv
if (req.method == "BAN") {
# ...
if (req.http.Varnish-Ban-TYPO3-Sitename) {
ban("req.url ~ " + req.url + " && obj.http.TYPO3-Sitename == " + req.http.Varnish-Ban-TYPO3-Sitename);
return(synth(200, "Banned " + req.url + " on site " + req.http.Varnish-Ban-TYPO3-Sitename));
}
}
Extra lines in vcl_deliver
### if we got a fileadmin doc from TYPO3 we don't want the browser to cache it
### but ask Varnish if a fresh version exists. The object can still be cached in varnish,
### and purged by the backend. @see \Site\Site\EventListener\VarnishEventListener
if (req.url ~ "(?i)^/fileadmin/[^?]*\.(csv|docx?|odt|pdf|pptx?|rtf|txt|xlsx?|xml)(\?.*)?$") {
set resp.http.Expires = 0;
set resp.http.Pragma = "no-cache";
set resp.http.Cache-Control = "no-cache, must-revalidate, max-age=0";
}
Edited by Rémy DANIEL