Move header_control from common to openedx/core

This commit is contained in:
Nimisha Asthagiri
2016-10-07 13:04:23 -04:00
parent fbdc34e7f4
commit 27c99e1cf2
9 changed files with 9 additions and 7 deletions

View File

@@ -10,12 +10,12 @@ from django.http import (
HttpResponseBadRequest, HttpResponseNotFound, HttpResponsePermanentRedirect)
from student.models import CourseEnrollment
from header_control import force_header_for_response
from xmodule.assetstore.assetmgr import AssetManager
from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG
from xmodule.modulestore import InvalidLocationError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locator import AssetLocator
from openedx.core.djangoapps.header_control import force_header_for_response
from .caching import get_cached_content, set_cached_content
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.exceptions import NotFoundError

View File

@@ -0,0 +1,22 @@
"""
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,68 @@
"""
Middleware decorator for removing headers.
"""
from functools import wraps
from openedx.core.djangoapps.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,24 @@
"""
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.
"""
for header in getattr(response, 'remove_headers', []):
del response[header]
for header, value in getattr(response, 'force_headers', {}).iteritems():
response[header] = value
return response

View File

@@ -0,0 +1,33 @@
"""Tests for remove_headers and force_header decorator. """
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from openedx.core.djangoapps.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,71 @@
"""Tests for header_control middleware."""
from django.http import HttpResponse, HttpRequest
from django.test import TestCase
from openedx.core.djangoapps.header_control import remove_headers_from_response, force_header_for_response
from openedx.core.djangoapps.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_doesnt_barf_if_not_modifying_anything(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'])
def test_doesnt_barf_removing_nonexistent_headers(self):
fake_request = HttpRequest()
fake_response = HttpResponse()
fake_response['Vary'] = 'Cookie'
fake_response['Accept-Encoding'] = 'gzip'
remove_headers_from_response(fake_response, 'Vary', 'FakeHeaderWeeee')
result = self.middleware.process_response(fake_request, fake_response)
self.assertNotIn('Vary', result)
self.assertEquals('gzip', result['Accept-Encoding'])
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'])