Commit 4ca2692f by Toby Lawrence

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.
parent 11024126
"""
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
"""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)
"""
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
...@@ -3,17 +3,48 @@ Middleware decorator for removing headers. ...@@ -3,17 +3,48 @@ Middleware decorator for removing headers.
""" """
from functools import wraps 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 clean_headers(*headers): def force_header(header, value):
""" """
Decorator that removes any headers specified from the response. Decorator that forces a header in the response to have a specific value.
Usage: Usage:
@clean_headers("Vary") @force_header("Vary", "Origin")
def myview(request): def myview(request):
... ...
The CleanHeadersMiddleware must be used and placed as closely as possible to the top 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. 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. This decorator is not safe for multiple uses: each call will overwrite any previously set values.
...@@ -28,7 +59,7 @@ def clean_headers(*headers): ...@@ -28,7 +59,7 @@ def clean_headers(*headers):
Alters the response. Alters the response.
""" """
response = func(*args, **kwargs) response = func(*args, **kwargs)
response.clean_headers = headers force_header_for_response(response, header, value)
return response return response
return _inner return _inner
......
""" """
Middleware used for cleaning headers from a response before it is sent to the end user. Middleware used for adjusting headers in a response before it is sent to the end user.
""" """
class CleanHeadersMiddleware(object): class HeaderControlMiddleware(object):
""" """
Middleware that can drop headers present in a response. 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. 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): def process_response(self, _request, response):
""" """
Processes the given response, potentially stripping out any unwanted headers. Processes the given response, potentially remove or modifying headers.
""" """
if len(getattr(response, 'clean_headers', [])) > 0: if len(getattr(response, 'remove_headers', [])) > 0:
for header in response.clean_headers: for header in response.remove_headers:
try: try:
del response[header] del response[header]
except KeyError: except KeyError:
pass 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 return response
"""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')
\ No newline at end of file
"""Tests for clean_headers middleware.""" """Tests for header_control middleware."""
from django.http import HttpResponse, HttpRequest from django.http import HttpResponse, HttpRequest
from django.test import TestCase from django.test import TestCase
from clean_headers.middleware import CleanHeadersMiddleware from header_control import remove_headers_from_response, force_header_for_response
from header_control.middleware import HeaderControlMiddleware
class TestCleanHeadersMiddlewareProcessResponse(TestCase): class TestHeaderControlMiddlewareProcessResponse(TestCase):
"""Test the `clean_headers` middleware. """ """Test the `header_control` middleware. """
def setUp(self): def setUp(self):
super(TestCleanHeadersMiddlewareProcessResponse, self).setUp() super(TestHeaderControlMiddlewareProcessResponse, self).setUp()
self.middleware = CleanHeadersMiddleware() self.middleware = HeaderControlMiddleware()
def test_cleans_intended_headers(self): def test_removes_intended_headers(self):
fake_request = HttpRequest() fake_request = HttpRequest()
fake_response = HttpResponse() fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie' fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip' fake_response['Accept-Encoding'] = 'gzip'
fake_response.clean_headers = ['Vary'] remove_headers_from_response(fake_response, 'Vary')
result = self.middleware.process_response(fake_request, fake_response) result = self.middleware.process_response(fake_request, fake_response)
self.assertNotIn('Vary', result) self.assertNotIn('Vary', result)
self.assertEquals('gzip', result['Accept-Encoding']) 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): def test_does_not_mangle_undecorated_response(self):
fake_request = HttpRequest() fake_request = HttpRequest()
......
...@@ -1087,7 +1087,7 @@ simplefilter('ignore') ...@@ -1087,7 +1087,7 @@ simplefilter('ignore')
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'request_cache.middleware.RequestCache', 'request_cache.middleware.RequestCache',
'clean_headers.middleware.CleanHeadersMiddleware', 'header_control.middleware.HeaderControlMiddleware',
'microsite_configuration.middleware.MicrositeMiddleware', 'microsite_configuration.middleware.MicrositeMiddleware',
'django_comment_client.middleware.AjaxExceptionMiddleware', 'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment