diff --git a/CHANGELOG.txt b/CHANGELOG.txt index eb50433438..54fc93bed3 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -30,6 +30,7 @@ Changelog * Fix: H2 elements in rich text fields were accidentally given a click() binding when put insite a collapsible multi field panel * Fix: The wagtailimages module is now compatible with remote storage backends that do not allow reopening closed files * Fix: Search no longer crashes when auto-indexing a model that doesn't have an id field (Scot Hacker) + * Fix: The `wagtailfrontendcache` module's HTTP backend has been rewritten to reliably direct requests to the configured cache hostname 1.0 (16.07.2015) diff --git a/docs/releases/1.1.rst b/docs/releases/1.1.rst index 37cec6c2c8..b37e6c53d3 100644 --- a/docs/releases/1.1.rst +++ b/docs/releases/1.1.rst @@ -66,6 +66,7 @@ Bug fixes * H2 elements in rich text fields were accidentally given a click() binding when put insite a collapsible multi field panel * The ``wagtailimages`` module is now compatible with remote storage backends that do not allow reopening closed files * Search no longer crashes when auto-indexing a model that doesn't have an ``id`` field + * The ``wagtailfrontendcache`` module's HTTP backend has been rewritten to reliably direct requests to the configured cache hostname Upgrade considerations diff --git a/setup.py b/setup.py index bd8fb70fc8..950d1549a7 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ install_requires = [ "beautifulsoup4>=4.3.2", "html5lib==0.999", "Unidecode>=0.04.14", - 'requests>=2.0.0', "Willow==0.2.1", ] diff --git a/tox.ini b/tox.ini index 20c41ea85c..bcf8bd6b31 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,6 @@ deps = html5lib==0.999 Unidecode>=0.04.14 six==1.7.3 - requests==2.3.0 elasticsearch==1.1.0 mock==1.0.1 python-dateutil==2.2 diff --git a/wagtail/contrib/wagtailfrontendcache/backends.py b/wagtail/contrib/wagtailfrontendcache/backends.py index 224e00dd26..f924e1cd17 100644 --- a/wagtail/contrib/wagtailfrontendcache/backends.py +++ b/wagtail/contrib/wagtailfrontendcache/backends.py @@ -1,9 +1,11 @@ import logging -import requests +import json -from django.utils.six.moves.urllib.parse import urlparse +from django.utils.six.moves.urllib.parse import urlparse, urlunparse, urlencode +from django.utils.six.moves.urllib.request import Request, urlopen +from django.utils.six.moves.urllib.error import URLError, HTTPError -from requests.adapters import HTTPAdapter +from wagtail.wagtailcore import __version__ logger = logging.getLogger('wagtail.frontendcache') @@ -15,44 +17,41 @@ class BaseBackend(object): class HTTPBackend(BaseBackend): - - class CustomHTTPAdapter(HTTPAdapter): - """ - Requests will always send requests to whatever server is in the netloc - part of the URL. This is a problem with purging the cache as this netloc - may point to a different server (such as an nginx instance running in - front of the cache). - - This class allows us to send a purge request directly to the cache server - with the host header still set correctly. It does this by changing the "url" - parameter of get_connection to always point to the cache server. Requests - will then use this connection to purge the page. - """ - def __init__(self, cache_url): - self.cache_url = cache_url - super(HTTPBackend.CustomHTTPAdapter, self).__init__() - - def get_connection(self, url, proxies=None): - return super(HTTPBackend.CustomHTTPAdapter, self).get_connection(self.cache_url, proxies) - - def __init__(self, params): - self.cache_location = params.pop('LOCATION') - - self.session = requests.Session() - self.session.mount('http://', self.CustomHTTPAdapter(self.cache_location)) + location_url_parsed = urlparse(params.pop('LOCATION')) + self.cache_scheme = location_url_parsed.scheme + self.cache_netloc = location_url_parsed.netloc def purge(self, url): - try: - response = self.session.request('PURGE', url) - except requests.ConnectionError: - logger.error("Couldn't purge '%s' from HTTP cache: Connection error", url) - return + url_parsed = urlparse(url) + host = url_parsed.hostname - # Check for error - if response.status_code != 200: - logger.error("Couldn't purge '%s' from HTTP cache: Didn't recieve a 200 response (instead, we got '%d %s')", url, response.status_code, response.reason) - return + # Append port to host if it is set in the original URL + if url_parsed.port: + host += (':' + str(url_parsed.port)) + + request = Request( + url=urlunparse([ + self.cache_scheme, + self.cache_netloc, + url_parsed.path, + url_parsed.params, + url_parsed.query, + url_parsed.fragment + ]), + headers={ + 'Host': host, + 'User-Agent': 'Wagtail-frontendcache/' + __version__ + }, + method='PURGE' + ) + + try: + urlopen(request) + except HTTPError as e: + logger.error("Couldn't purge '%s' from HTTP cache. HTTPError: %d %s", url, e.code, e.reason) + except URLError as e: + logger.error("Couldn't purge '%s' from HTTP cache. URLError: %s", url, e.reason) class CloudflareBackend(BaseBackend): @@ -62,23 +61,21 @@ class CloudflareBackend(BaseBackend): def purge(self, url): try: - response = requests.post('https://www.cloudflare.com/api_json.html', { + response = urlopen('https://www.cloudflare.com/api_json.html', data=urlencode({ 'email': self.cloudflare_email, 'tkn': self.cloudflare_token, 'a': 'zone_file_purge', 'z': urlparse(url).netloc, 'url': url - }) - except requests.ConnectionError: - logger.error("Couldn't purge '%s' from Cloudflare: Connection error", url) + }).encode('utf-8')) + except HTTPError as e: + logger.error("Couldn't purge '%s' from Cloudflare. HTTPError: %d %s", url, e.code, e.reason) + return + except URLError as e: + logger.error("Couldn't purge '%s' from Cloudflare. URLError: %s", url, e.reason) return - # Check for error - if response.status_code != 200: - logger.error("Couldn't purge '%s' from Cloudflare: Didn't recieve a 200 response (instead, we got '%d %s')", url, response.status_code, response.reason) - return - - response_json = response.json() + response_json = json.loads(response.read().decode('utf-8')) if response_json['result'] == 'error': - logger.error("Couldn't purge '%s' from Cloudflare: Cloudflare error '%s'", url, response_json['msg']) + logger.error("Couldn't purge '%s' from Cloudflare. Cloudflare error '%s'", url, response_json['msg']) return diff --git a/wagtail/contrib/wagtailfrontendcache/tests.py b/wagtail/contrib/wagtailfrontendcache/tests.py index 52751c7405..69825ffe38 100644 --- a/wagtail/contrib/wagtailfrontendcache/tests.py +++ b/wagtail/contrib/wagtailfrontendcache/tests.py @@ -25,7 +25,8 @@ class TestBackendConfiguration(TestCase): self.assertEqual(set(backends.keys()), set(['varnish'])) self.assertIsInstance(backends['varnish'], HTTPBackend) - self.assertEqual(backends['varnish'].cache_location, 'http://localhost:8000') + self.assertEqual(backends['varnish'].cache_scheme, 'http') + self.assertEqual(backends['varnish'].cache_netloc, 'localhost:8000') def test_cloudflare(self): backends = get_backends(backend_settings={ @@ -78,7 +79,8 @@ class TestBackendConfiguration(TestCase): self.assertEqual(set(backends.keys()), set(['default'])) self.assertIsInstance(backends['default'], HTTPBackend) - self.assertEqual(backends['default'].cache_location, 'http://localhost:8000') + self.assertEqual(backends['default'].cache_scheme, 'http') + self.assertEqual(backends['default'].cache_netloc, 'localhost:8000') PURGED_URLS = []