Rename 'clean_headers' to 'header_control'.

This is part of adding the ability to forcefully set headers through the
middleware in addition to removing specific headers.
This commit is contained in:
Toby Lawrence
2016-03-03 14:00:12 -05:00
parent 1102412603
commit 4ca2692f5d
11 changed files with 202 additions and 131 deletions

View File

@@ -1,15 +0,0 @@
"""
This middleware is used for cleaning headers from a response before it is sent to the end user.
Due to the nature of how middleware runs, a piece of middleware high in the chain cannot ensure
that response headers won't be present on the final response body, as middleware further down
the chain could be adding them.
This middleware is intended to sit as close as possible to the top of the list, so that it has
a chance on the reponse going out to strip the intended headers.
"""
def remove_headers_from_response(response, *headers):
"""Removes the given headers from the response using the clean_headers middleware."""
response.clean_headers = headers

View File

@@ -1,36 +0,0 @@
"""
Middleware decorator for removing headers.
"""
from functools import wraps
def clean_headers(*headers):
"""
Decorator that removes any headers specified from the response.
Usage:
@clean_headers("Vary")
def myview(request):
...
The CleanHeadersMiddleware must be used and placed as closely as possible to the top
of the middleware chain, ideally after any caching middleware but before everything else.
This decorator is not safe for multiple uses: each call will overwrite any previously set values.
"""
def _decorator(func):
"""
Decorates the given function.
"""
@wraps(func)
def _inner(*args, **kwargs):
"""
Alters the response.
"""
response = func(*args, **kwargs)
response.clean_headers = headers
return response
return _inner
return _decorator

View File

@@ -1,25 +0,0 @@
"""
Middleware used for cleaning headers from a response before it is sent to the end user.
"""
class CleanHeadersMiddleware(object):
"""
Middleware that can drop headers present in a response.
This can be used, for example, to remove headers i.e. drop any Vary headers to improve cache performance.
"""
def process_response(self, _request, response):
"""
Processes the given response, potentially stripping out any unwanted headers.
"""
if len(getattr(response, 'clean_headers', [])) > 0:
for header in response.clean_headers:
try:
del response[header]
except KeyError:
pass
return response

View File

@@ -1,20 +0,0 @@
"""Tests for clean_headers decorator. """
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from clean_headers.decorators import clean_headers
def fake_view(_request):
"""Fake view that returns an empty response."""
return HttpResponse()
class TestCleanHeaders(TestCase):
"""Test the `clean_headers` decorator."""
def test_clean_headers(self):
request = HttpRequest()
wrapper = clean_headers('Vary', 'Accept-Encoding')
wrapped_view = wrapper(fake_view)
response = wrapped_view(request)
self.assertEqual(len(response.clean_headers), 2)

View File

@@ -1,34 +0,0 @@
"""Tests for clean_headers middleware."""
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from clean_headers.middleware import CleanHeadersMiddleware
class TestCleanHeadersMiddlewareProcessResponse(TestCase):
"""Test the `clean_headers` middleware. """
def setUp(self):
super(TestCleanHeadersMiddlewareProcessResponse, self).setUp()
self.middleware = CleanHeadersMiddleware()
def test_cleans_intended_headers(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
fake_response.clean_headers = ['Vary']
result = self.middleware.process_response(fake_request, fake_response)
self.assertNotIn('Vary', result)
self.assertEquals('gzip', result['Accept-Encoding'])
def test_does_not_mangle_undecorated_response(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
result = self.middleware.process_response(fake_request, fake_response)
self.assertEquals('Cookie', result['Vary'])
self.assertEquals('gzip', result['Accept-Encoding'])

View File

@@ -0,0 +1,21 @@
"""
This middleware is used for adjusting the headers in a response before it is sent to the end user.
This middleware is intended to sit as close as possible to the top of the middleare list as possible,
so that it is one of the last pieces of middleware to touch the response, and thus can most accurately
adjust/control the headers of the response.
"""
def remove_headers_from_response(response, *headers):
"""Removes the given headers from the response using the header_control middleware."""
response.remove_headers = headers
def force_header_for_response(response, header, value):
"""Forces the given header for the given response using the header_control middleware."""
force_headers = {}
if hasattr(response, 'force_headers'):
force_headers = response.force_headers
force_headers[header] = value
response.force_headers = force_headers

View File

@@ -0,0 +1,67 @@
"""
Middleware decorator for removing headers.
"""
from functools import wraps
from header_control import remove_headers_from_response, force_header_for_response
def remove_headers(*headers):
"""
Decorator that removes specific headers from the response.
Usage:
@remove_headers("Vary")
def myview(request):
...
The HeaderControlMiddleware must be used and placed as closely as possible to the top
of the middleware chain, ideally after any caching middleware but before everything else.
This decorator is not safe for multiple uses: each call will overwrite any previously set values.
"""
def _decorator(func):
"""
Decorates the given function.
"""
@wraps(func)
def _inner(*args, **kwargs):
"""
Alters the response.
"""
response = func(*args, **kwargs)
remove_headers_from_response(response, *headers)
return response
return _inner
return _decorator
def force_header(header, value):
"""
Decorator that forces a header in the response to have a specific value.
Usage:
@force_header("Vary", "Origin")
def myview(request):
...
The HeaderControlMiddleware must be used and placed as closely as possible to the top
of the middleware chain, ideally after any caching middleware but before everything else.
This decorator is not safe for multiple uses: each call will overwrite any previously set values.
"""
def _decorator(func):
"""
Decorates the given function.
"""
@wraps(func)
def _inner(*args, **kwargs):
"""
Alters the response.
"""
response = func(*args, **kwargs)
force_header_for_response(response, header, value)
return response
return _inner
return _decorator

View File

@@ -0,0 +1,34 @@
"""
Middleware used for adjusting headers in a response before it is sent to the end user.
"""
class HeaderControlMiddleware(object):
"""
Middleware that can modify/remove headers in a response.
This can be used, for example, to remove headers i.e. drop any Vary headers to improve cache performance.
"""
def process_response(self, _request, response):
"""
Processes the given response, potentially remove or modifying headers.
"""
if len(getattr(response, 'remove_headers', [])) > 0:
for header in response.remove_headers:
try:
del response[header]
except KeyError:
pass
if len(getattr(response, 'force_headers', {})) > 0:
for header, value in response.force_headers.iteritems():
try:
del response[header]
except KeyError:
pass
response[header] = value
return response

View File

@@ -0,0 +1,32 @@
"""Tests for remove_headers and force_header decorator. """
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from header_control.decorators import remove_headers, force_header
def fake_view(_request):
"""Fake view that returns an empty response."""
return HttpResponse()
class TestRemoveHeaders(TestCase):
"""Test the `remove_headers` decorator."""
def test_remove_headers(self):
request = HttpRequest()
wrapper = remove_headers('Vary', 'Accept-Encoding')
wrapped_view = wrapper(fake_view)
response = wrapped_view(request)
self.assertEqual(len(response.remove_headers), 2)
class TestForceHeader(TestCase):
"""Test the `force_header` decorator."""
def test_force_header(self):
request = HttpRequest()
wrapper = force_header('Vary', 'Origin')
wrapped_view = wrapper(fake_view)
response = wrapped_view(request)
self.assertEqual(len(response.force_headers), 1)
self.assertEqual(response.force_headers['Vary'], 'Origin')

View File

@@ -0,0 +1,47 @@
"""Tests for header_control middleware."""
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from header_control import remove_headers_from_response, force_header_for_response
from header_control.middleware import HeaderControlMiddleware
class TestHeaderControlMiddlewareProcessResponse(TestCase):
"""Test the `header_control` middleware. """
def setUp(self):
super(TestHeaderControlMiddlewareProcessResponse, self).setUp()
self.middleware = HeaderControlMiddleware()
def test_removes_intended_headers(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
remove_headers_from_response(fake_response, 'Vary')
result = self.middleware.process_response(fake_request, fake_response)
self.assertNotIn('Vary', result)
self.assertEquals('gzip', result['Accept-Encoding'])
def test_forces_intended_header(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
force_header_for_response(fake_response, 'Vary', 'Origin')
result = self.middleware.process_response(fake_request, fake_response)
self.assertEquals('Origin', result['Vary'])
self.assertEquals('gzip', result['Accept-Encoding'])
def test_does_not_mangle_undecorated_response(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
result = self.middleware.process_response(fake_request, fake_response)
self.assertEquals('Cookie', result['Vary'])
self.assertEquals('gzip', result['Accept-Encoding'])

View File

@@ -1087,7 +1087,7 @@ simplefilter('ignore')
MIDDLEWARE_CLASSES = (
'request_cache.middleware.RequestCache',
'clean_headers.middleware.CleanHeadersMiddleware',
'header_control.middleware.HeaderControlMiddleware',
'microsite_configuration.middleware.MicrositeMiddleware',
'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware',