Move header_control from common to openedx/core
This commit is contained in:
@@ -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
|
||||
|
||||
22
openedx/core/djangoapps/header_control/__init__.py
Normal file
22
openedx/core/djangoapps/header_control/__init__.py
Normal 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
|
||||
68
openedx/core/djangoapps/header_control/decorators.py
Normal file
68
openedx/core/djangoapps/header_control/decorators.py
Normal 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
|
||||
24
openedx/core/djangoapps/header_control/middleware.py
Normal file
24
openedx/core/djangoapps/header_control/middleware.py
Normal 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
|
||||
@@ -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')
|
||||
@@ -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'])
|
||||
Reference in New Issue
Block a user