diff --git a/wagtail/contrib/frontend_cache/backends.py b/wagtail/contrib/frontend_cache/backends.py index ffbdcabc5..09fd57e85 100644 --- a/wagtail/contrib/frontend_cache/backends.py +++ b/wagtail/contrib/frontend_cache/backends.py @@ -66,6 +66,8 @@ class HTTPBackend(BaseBackend): class CloudflareBackend(BaseBackend): + CHUNK_SIZE = 30 + def __init__(self, params): self.cloudflare_email = params.pop("EMAIL", None) self.cloudflare_api_key = ( @@ -84,8 +86,7 @@ class CloudflareBackend(BaseBackend): "The setting 'WAGTAILFRONTENDCACHE' requires both 'EMAIL' and 'API_KEY', or 'BEARER_TOKEN' to be specified." ) - - def purge_batch(self, urls): + def _purge_urls(self, urls): try: purge_url = 'https://api.cloudflare.com/client/v4/zones/{0}/purge_cache'.format(self.cloudflare_zoneid) @@ -125,6 +126,13 @@ class CloudflareBackend(BaseBackend): logger.error("Couldn't purge '%s' from Cloudflare. Cloudflare errors '%s'", url, error_messages) return + def purge_batch(self, urls): + # Break the batched URLs in to chunks to fit within Cloudflare's maximum size for + # the purge_cache call (https://api.cloudflare.com/#zone-purge-files-by-url) + for i in range(0, len(urls), self.CHUNK_SIZE): + chunk = urls[i:i + self.CHUNK_SIZE] + self._purge_urls(chunk) + def purge(self, url): self.purge_batch([url]) diff --git a/wagtail/contrib/frontend_cache/tests.py b/wagtail/contrib/frontend_cache/tests.py index e1602b968..d82eb2678 100644 --- a/wagtail/contrib/frontend_cache/tests.py +++ b/wagtail/contrib/frontend_cache/tests.py @@ -198,6 +198,17 @@ class MockBackend(BaseBackend): PURGED_URLS.append(url) +class MockCloudflareBackend(CloudflareBackend): + def __init__(self, config): + pass + + def _purge_urls(self, urls): + if len(urls) > self.CHUNK_SIZE: + raise Exception("Cloudflare backend is not chunking requests as expected") + + PURGED_URLS.extend(urls) + + @override_settings(WAGTAILFRONTENDCACHE={ 'varnish': { 'BACKEND': 'wagtail.contrib.frontend_cache.tests.MockBackend', @@ -238,6 +249,25 @@ class TestCachePurgingFunctions(TestCase): self.assertEqual(PURGED_URLS, ['http://localhost/events/', 'http://localhost/events/past/', 'http://localhost/foo']) +@override_settings(WAGTAILFRONTENDCACHE={ + 'cloudflare': { + 'BACKEND': 'wagtail.contrib.frontend_cache.tests.MockCloudflareBackend', + }, +}) +class TestCloudflareCachePurgingFunctions(TestCase): + def setUp(self): + # Reset PURGED_URLS to an empty list + PURGED_URLS[:] = [] + + def test_cloudflare_purge_batch_chunked(self): + batch = PurgeBatch() + urls = ['https://localhost/foo{}'.format(i) for i in range(1, 65)] + batch.add_urls(urls) + batch.purge() + + self.assertCountEqual(PURGED_URLS, urls) + + @override_settings(WAGTAILFRONTENDCACHE={ 'varnish': { 'BACKEND': 'wagtail.contrib.frontend_cache.tests.MockBackend',