2
0

frontendcache.rst 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. .. _frontend_cache_purging:
  2. Frontend cache invalidator
  3. ==========================
  4. Many websites use a frontend cache such as Varnish, Squid, Cloudflare or CloudFront to gain extra performance. The downside of using a frontend cache though is that they don't respond well to updating content and will often keep an old version of a page cached after it has been updated.
  5. This document describes how to configure Wagtail to purge old versions of pages from a frontend cache whenever a page gets updated.
  6. Setting it up
  7. -------------
  8. Firstly, add ``"wagtail.contrib.frontend_cache"`` to your INSTALLED_APPS:
  9. .. code-block:: python
  10. INSTALLED_APPS = [
  11. ...
  12. "wagtail.contrib.frontend_cache"
  13. ]
  14. The ``wagtailfrontendcache`` module provides a set of signal handlers which will automatically purge the cache whenever a page is published or deleted. These signal handlers are automatically registered when the ``wagtail.contrib.frontend_cache`` app is loaded.
  15. Varnish/Squid
  16. ^^^^^^^^^^^^^
  17. Add a new item into the ``WAGTAILFRONTENDCACHE`` setting and set the ``BACKEND`` parameter to ``wagtail.contrib.frontend_cache.backends.HTTPBackend``. This backend requires an extra parameter ``LOCATION`` which points to where the cache is running (this must be a direct connection to the server and cannot go through another proxy).
  18. .. code-block:: python
  19. # settings.py
  20. WAGTAILFRONTENDCACHE = {
  21. 'varnish': {
  22. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.HTTPBackend',
  23. 'LOCATION': 'http://localhost:8000',
  24. },
  25. }
  26. WAGTAILFRONTENDCACHE_LANGUAGES = []
  27. Set ``WAGTAILFRONTENDCACHE_LANGUAGES`` to a list of languages (typically equal to ``[l[0] for l in settings.LANGUAGES]``) to also purge the urls for each language of a purging url. This setting needs ``settings.USE_I18N`` to be ``True`` to work. Its default is an empty list.
  28. Finally, make sure you have configured your frontend cache to accept PURGE requests:
  29. - `Varnish <https://www.varnish-cache.org/docs/3.0/tutorial/purging.html>`_
  30. - `Squid <https://wiki.squid-cache.org/SquidFaq/OperatingSquid#How_can_I_purge_an_object_from_my_cache.3F>`_
  31. .. _frontendcache_cloudflare:
  32. Cloudflare
  33. ^^^^^^^^^^
  34. Firstly, you need to register an account with Cloudflare if you haven't already got one. You can do this here: `Cloudflare Sign up <https://www.cloudflare.com/sign-up>`_
  35. Add an item into the ``WAGTAILFRONTENDCACHE`` and set the ``BACKEND`` parameter to ``wagtail.contrib.frontend_cache.backends.CloudflareBackend``.
  36. This backend can be configured to use an account-wide API key, or an API token with restricted access.
  37. To use an account-wide API key, find the key `as described in the Cloudflare documentation <https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys#12345682>`_ and specify ``EMAIL`` and ``API_KEY`` parameters.
  38. To use a limited API token, `create a token <https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys#12345680>`_ configured with the 'Zone, Cache Purge' permission and specify the ``BEARER_TOKEN`` parameter.
  39. A ``ZONEID`` parameter will need to be set for either option. To find the ``ZONEID`` for your domain, read the `Cloudflare API Documentation <https://api.cloudflare.com/#getting-started-resource-ids>`_
  40. With an API key:
  41. .. code-block:: python
  42. # settings.py
  43. WAGTAILFRONTENDCACHE = {
  44. 'cloudflare': {
  45. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudflareBackend',
  46. 'EMAIL': 'your-cloudflare-email-address@example.com',
  47. 'API_KEY': 'your cloudflare api key',
  48. 'ZONEID': 'your cloudflare domain zone id',
  49. },
  50. }
  51. With an API token:
  52. .. code-block:: python
  53. # settings.py
  54. WAGTAILFRONTENDCACHE = {
  55. 'cloudflare': {
  56. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudflareBackend',
  57. 'BEARER_TOKEN': 'your cloudflare bearer token',
  58. 'ZONEID': 'your cloudflare domain zone id',
  59. },
  60. }
  61. .. _frontendcache_aws_cloudfront:
  62. Amazon CloudFront
  63. ^^^^^^^^^^^^^^^^^
  64. Within Amazon Web Services you will need at least one CloudFront web distribution. If you don't have one, you can get one here: `CloudFront getting started <https://aws.amazon.com/cloudfront/>`_
  65. Add an item into the ``WAGTAILFRONTENDCACHE`` and set the ``BACKEND`` parameter to ``wagtail.contrib.frontend_cache.backends.CloudfrontBackend``. This backend requires one extra parameter, ``DISTRIBUTION_ID`` (your CloudFront generated distribution id).
  66. .. code-block:: python
  67. WAGTAILFRONTENDCACHE = {
  68. 'cloudfront': {
  69. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudfrontBackend',
  70. 'DISTRIBUTION_ID': 'your-distribution-id',
  71. },
  72. }
  73. Configuration of credentials can done in multiple ways. You won't need to store them in your Django settings file. You can read more about this here: `Boto 3 Docs <https://boto3.readthedocs.org/en/latest/guide/configuration.html>`_
  74. In case you run multiple sites with Wagtail and each site has its CloudFront distribution, provide a mapping instead of a single distribution. Make sure the mapping matches with the hostnames provided in your site settings.
  75. .. code-block:: python
  76. WAGTAILFRONTENDCACHE = {
  77. 'cloudfront': {
  78. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudfrontBackend',
  79. 'DISTRIBUTION_ID': {
  80. 'www.wagtail.org': 'your-distribution-id',
  81. 'www.madewithwagtail.org': 'your-distribution-id',
  82. },
  83. },
  84. }
  85. .. note::
  86. In most cases, absolute URLs with ``www`` prefixed domain names should be used in your mapping. Only drop the ``www`` prefix if you're absolutely sure you're not using it (e.g. a subdomain).
  87. Azure CDN
  88. ^^^^^^^^^
  89. With `Azure CDN <https://azure.microsoft.com/en-gb/services/cdn/>`_ you will need a CDN profile with an endpoint configured.
  90. .. _azure-mgmt-cdn: https://pypi.org/project/azure-mgmt-cdn/
  91. .. _azure-identity: https://pypi.org/project/azure-identity/
  92. .. _azure-mgmt-resource: https://pypi.org/project/azure-mgmt-resource/
  93. The third-party dependencies of this backend are:
  94. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  95. | PyPI Package | Essential | Reason |
  96. +=========================+===========+=======================================================================================================================================+
  97. | azure-mgmt-cdn_ | Yes | Interacting with the CDN service. |
  98. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  99. | azure-identity_ | No | Obtaining credentials. It's optional if you want to specify your own credential using a ``CREDENTIALS`` setting (more details below). |
  100. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  101. | azure-mgmt-resource_ | No | For obtaining the subscription ID. Redundant if you want to explicitly specify a ``SUBSCRIPTION_ID`` setting (more details below). |
  102. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  103. Add an item into the ``WAGTAILFRONTENDCACHE`` and set the ``BACKEND`` parameter to ``wagtail.contrib.frontend_cache.backends.AzureCdnBackend``. This backend requires the following settings to be set:
  104. * ``RESOURCE_GROUP_NAME`` - the resource group that your CDN profile is in.
  105. * ``CDN_PROFILE_NAME`` - the profile name of the CDN service that you want to use.
  106. * ``CDN_ENDPOINT_NAME`` - the name of the endpoint you want to be purged.
  107. .. code-block:: python
  108. WAGTAILFRONTENDCACHE = {
  109. 'azure_cdn': {
  110. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.AzureCdnBackend',
  111. 'RESOURCE_GROUP_NAME': 'MY-WAGTAIL-RESOURCE-GROUP',
  112. 'CDN_PROFILE_NAME': 'wagtailio',
  113. 'CDN_ENDPOINT_NAME': 'wagtailio-cdn-endpoint-123',
  114. },
  115. }
  116. By default the credentials will use ``azure.identity.DefaultAzureCredential``. To modify the credential object used, please use ``CREDENTIALS`` setting. Read about your options on the `Azure documentation <https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-authenticate>`_.
  117. .. code-block:: python
  118. from azure.common.credentials import ServicePrincipalCredentials
  119. WAGTAILFRONTENDCACHE = {
  120. 'azure_cdn': {
  121. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.AzureCdnBackend',
  122. 'RESOURCE_GROUP_NAME': 'MY-WAGTAIL-RESOURCE-GROUP',
  123. 'CDN_PROFILE_NAME': 'wagtailio',
  124. 'CDN_ENDPOINT_NAME': 'wagtailio-cdn-endpoint-123',
  125. 'CREDENTIALS': ServicePrincipalCredentials(
  126. client_id='your client id',
  127. secret='your client secret',
  128. )
  129. },
  130. }
  131. Another option that can be set is ``SUBSCRIPTION_ID``. By default the first encountered subscription will be used, but if your credential has access to more subscriptions, you should set this to an explicit value.
  132. Azure Front Door
  133. ^^^^^^^^^^^^^^^^
  134. With `Azure Front Door <https://azure.microsoft.com/en-gb/services/frontdoor/>`_ you will need a Front Door instance with caching enabled.
  135. .. _azure-mgmt-frontdoor: https://pypi.org/project/azure-mgmt-frontdoor/
  136. .. _azure-identity: https://pypi.org/project/azure-identity/
  137. .. _azure-mgmt-resource: https://pypi.org/project/azure-mgmt-resource/
  138. The third-party dependencies of this backend are:
  139. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  140. | PyPI Package | Essential | Reason |
  141. +=========================+===========+=======================================================================================================================================+
  142. | azure-mgmt-frontdoor_ | Yes | Interacting with the Front Door service. |
  143. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  144. | azure-identity_ | No | Obtaining credentials. It's optional if you want to specify your own credential using a ``CREDENTIALS`` setting (more details below). |
  145. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  146. | azure-mgmt-resource_ | No | For obtaining the subscription ID. Redundant if you want to explicitly specify a ``SUBSCRIPTION_ID`` setting (more details below). |
  147. +-------------------------+-----------+---------------------------------------------------------------------------------------------------------------------------------------+
  148. Add an item into the ``WAGTAILFRONTENDCACHE`` and set the ``BACKEND`` parameter to ``wagtail.contrib.frontend_cache.backends.AzureFrontDoorBackend``. This backend requires the following settings to be set:
  149. * ``RESOURCE_GROUP_NAME`` - the resource group that your Front Door instance is part of.
  150. * ``FRONT_DOOR_NAME`` - your configured Front Door instance name.
  151. .. code-block:: python
  152. WAGTAILFRONTENDCACHE = {
  153. 'azure_front_door': {
  154. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.AzureFrontDoorBackend',
  155. 'RESOURCE_GROUP_NAME': 'MY-WAGTAIL-RESOURCE-GROUP',
  156. 'FRONT_DOOR_NAME': 'wagtail-io-front-door',
  157. },
  158. }
  159. By default the credentials will use ``azure.identity.DefaultAzureCredential``. To modify the credential object used, please use ``CREDENTIALS`` setting. Read about your options on the `Azure documentation <https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-authenticate>`_.
  160. .. code-block:: python
  161. from azure.common.credentials import ServicePrincipalCredentials
  162. WAGTAILFRONTENDCACHE = {
  163. 'azure_front_door': {
  164. 'BACKEND': 'wagtail.contrib.frontend_cache.backends.AzureFrontDoorBackend',
  165. 'RESOURCE_GROUP_NAME': 'MY-WAGTAIL-RESOURCE-GROUP',
  166. 'FRONT_DOOR_NAME': 'wagtail-io-front-door',
  167. 'CREDENTIALS': ServicePrincipalCredentials(
  168. client_id='your client id',
  169. secret='your client secret',
  170. )
  171. },
  172. }
  173. Another option that can be set is ``SUBSCRIPTION_ID``. By default the first encountered subscription will be used, but if your credential has access to more subscriptions, you should set this to an explicit value.
  174. Advanced usage
  175. --------------
  176. Invalidating more than one URL per page
  177. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  178. By default, Wagtail will only purge one URL per page. If your page has more than one URL to be purged, you will need to override the ``get_cached_paths`` method on your page type.
  179. .. code-block:: python
  180. class BlogIndexPage(Page):
  181. def get_blog_items(self):
  182. # This returns a Django paginator of blog items in this section
  183. return Paginator(self.get_children().live().type(BlogPage), 10)
  184. def get_cached_paths(self):
  185. # Yield the main URL
  186. yield '/'
  187. # Yield one URL per page in the paginator to make sure all pages are purged
  188. for page_number in range(1, self.get_blog_items().num_pages + 1):
  189. yield '/?page=' + str(page_number)
  190. Invalidating index pages
  191. ^^^^^^^^^^^^^^^^^^^^^^^^
  192. Pages that list other pages (such as a blog index) may need to be purged as
  193. well so any changes to a blog page are also reflected on the index (for example,
  194. a blog post was added, deleted or its title/thumbnail was changed).
  195. To purge these pages, we need to write a signal handler that listens for
  196. Wagtail's ``page_published`` and ``page_unpublished`` signals for blog pages
  197. (note, ``page_published`` is called both when a page is created and updated).
  198. This signal handler would trigger the invalidation of the index page using the
  199. ``PurgeBatch`` class which is used to construct and dispatch invalidation requests.
  200. .. code-block:: python
  201. # models.py
  202. from django.dispatch import receiver
  203. from django.db.models.signals import pre_delete
  204. from wagtail.core.signals import page_published
  205. from wagtail.contrib.frontend_cache.utils import PurgeBatch
  206. ...
  207. def blog_page_changed(blog_page):
  208. # Find all the live BlogIndexPages that contain this blog_page
  209. batch = PurgeBatch()
  210. for blog_index in BlogIndexPage.objects.live():
  211. if blog_page in blog_index.get_blog_items().object_list:
  212. batch.add_page(blog_index)
  213. # Purge all the blog indexes we found in a single request
  214. batch.purge()
  215. @receiver(page_published, sender=BlogPage)
  216. def blog_published_handler(instance, **kwargs):
  217. blog_page_changed(instance)
  218. @receiver(pre_delete, sender=BlogPage)
  219. def blog_deleted_handler(instance, **kwargs):
  220. blog_page_changed(instance)
  221. .. _frontend_cache_invalidating_urls:
  222. Invalidating URLs
  223. ^^^^^^^^^^^^^^^^^
  224. The ``PurgeBatch`` class provides a ``.add_url(url)`` and a ``.add_urls(urls)``
  225. for adding individual URLs to the purge batch.
  226. For example, this could be useful for purging a single page on a blog index:
  227. .. code-block:: python
  228. from wagtail.contrib.frontend_cache.utils import PurgeBatch
  229. # Purge the first page of the blog index
  230. batch = PurgeBatch()
  231. batch.add_url(blog_index.url + '?page=1')
  232. batch.purge()
  233. The ``PurgeBatch`` class
  234. ^^^^^^^^^^^^^^^^^^^^^^^^
  235. All of the methods available on ``PurgeBatch`` are listed below:
  236. .. automodule:: wagtail.contrib.frontend_cache.utils
  237. .. autoclass:: PurgeBatch
  238. .. automethod:: add_url
  239. .. automethod:: add_urls
  240. .. automethod:: add_page
  241. .. automethod:: add_pages
  242. .. automethod:: purge