From bb0b4b705b508451567bcada9106b91b8fca9e86 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 25 Apr 2016 07:56:07 -0400 Subject: Fixed #26052 -- Moved conditional_content_removal() processing to the test client. --- django/core/handlers/base.py | 15 ----- django/http/__init__.py | 2 - django/http/utils.py | 28 --------- django/test/client.py | 24 ++++++++ docs/releases/1.10.txt | 5 ++ tests/http_utils/tests.py | 71 ---------------------- .../test_conditional_content_removal.py | 71 ++++++++++++++++++++++ 7 files changed, 100 insertions(+), 116 deletions(-) delete mode 100644 django/http/utils.py delete mode 100644 tests/http_utils/tests.py create mode 100644 tests/test_client/test_conditional_content_removal.py diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index cbdaea5a97..93b8f04cac 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -24,10 +24,6 @@ logger = logging.getLogger('django.request') class BaseHandler(object): - # Changes that are always applied to a response (in this order). - response_fixes = [ - http.conditional_content_removal, - ] def __init__(self): self._request_middleware = None @@ -230,7 +226,6 @@ class BaseHandler(object): "%s.process_response didn't return an " "HttpResponse object. It returned None instead." % (middleware_method.__self__.__class__.__name__)) - response = self.apply_response_fixes(request, response) except Exception: # Any exception should be gathered and handled signals.got_request_exception.send(sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) @@ -289,13 +284,3 @@ class BaseHandler(object): # Return an HttpResponse that displays a friendly error message. callback, param_dict = resolver.resolve_error_handler(500) return callback(request, **param_dict) - - def apply_response_fixes(self, request, response): - """ - Applies each of the functions in self.response_fixes to the request and - response, modifying the response in the process. Returns the new - response. - """ - for func in self.response_fixes: - response = func(request, response) - return response diff --git a/django/http/__init__.py b/django/http/__init__.py index 00a303c11c..491239bf8a 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -9,7 +9,6 @@ from django.http.response import ( HttpResponsePermanentRedirect, HttpResponseRedirect, HttpResponseServerError, JsonResponse, StreamingHttpResponse, ) -from django.http.utils import conditional_content_removal __all__ = [ 'SimpleCookie', 'parse_cookie', 'HttpRequest', 'QueryDict', @@ -19,5 +18,4 @@ __all__ = [ 'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound', 'HttpResponseNotAllowed', 'HttpResponseGone', 'HttpResponseServerError', 'Http404', 'BadHeaderError', 'JsonResponse', 'FileResponse', - 'conditional_content_removal', ] diff --git a/django/http/utils.py b/django/http/utils.py deleted file mode 100644 index 3ea71cb892..0000000000 --- a/django/http/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Functions that modify an HTTP request or response in some way. -""" - -# This group of functions are run as part of the response handling, after -# everything else, including all response middleware. Think of them as -# "compulsory response middleware". Be careful about what goes here, because -# it's a little fiddly to override this behavior, so they should be truly -# universally applicable. - - -def conditional_content_removal(request, response): - """ - Removes the content of responses for HEAD requests, 1xx, 204 and 304 - responses. Ensures compliance with RFC 2616, section 4.3. - """ - if 100 <= response.status_code < 200 or response.status_code in (204, 304): - if response.streaming: - response.streaming_content = [] - else: - response.content = b'' - response['Content-Length'] = '0' - if request.method == 'HEAD': - if response.streaming: - response.streaming_content = [] - else: - response.content = b'' - return response diff --git a/django/test/client.py b/django/test/client.py index 8349a902fa..166e6ef877 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -92,6 +92,26 @@ def closing_iterator_wrapper(iterable, close): request_finished.connect(close_old_connections) +def conditional_content_removal(request, response): + """ + Simulate the behavior of most Web servers by removing the content of + responses for HEAD requests, 1xx, 204, and 304 responses. Ensures + compliance with RFC 2616, section 4.3. + """ + if 100 <= response.status_code < 200 or response.status_code in (204, 304): + if response.streaming: + response.streaming_content = [] + else: + response.content = b'' + response['Content-Length'] = '0' + if request.method == 'HEAD': + if response.streaming: + response.streaming_content = [] + else: + response.content = b'' + return response + + class ClientHandler(BaseHandler): """ A HTTP Handler that can be used for testing purposes. Uses the WSGI @@ -120,6 +140,10 @@ class ClientHandler(BaseHandler): # Request goes through middleware. response = self.get_response(request) + + # Simulate behaviors of most Web servers. + conditional_content_removal(request, response) + # Attach the originating request to the response so that it could be # later retrieved. response.wsgi_request = request diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index a7a7077bc3..f688758a91 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -778,6 +778,11 @@ Miscellaneous attribute to ``False`` to disable it. You could also add the ``novalidate`` attribute to ``
`` if you don't want browser validation. +* The WSGI handler no longer removes content of responses from ``HEAD`` + requests or responses with a ``status_code`` of 100-199, 204, or 304. Most + Web servers already implement this behavior. Responses retrieved using the + Django test client continue to have these "response fixes" applied. + .. _deprecated-features-1.10: Features deprecated in 1.10 diff --git a/tests/http_utils/tests.py b/tests/http_utils/tests.py deleted file mode 100644 index 53e06c8938..0000000000 --- a/tests/http_utils/tests.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import unicode_literals - -import gzip -import io - -from django.http import HttpRequest, HttpResponse, StreamingHttpResponse -from django.http.utils import conditional_content_removal -from django.test import SimpleTestCase - - -# based on Python 3.3's gzip.compress -def gzip_compress(data): - buf = io.BytesIO() - f = gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=0) - try: - f.write(data) - finally: - f.close() - return buf.getvalue() - - -class HttpUtilTests(SimpleTestCase): - - def test_conditional_content_removal(self): - """ - Tests that content is removed from regular and streaming responses with - a status_code of 100-199, 204, 304 or a method of "HEAD". - """ - req = HttpRequest() - - # Do nothing for 200 responses. - res = HttpResponse('abc') - conditional_content_removal(req, res) - self.assertEqual(res.content, b'abc') - - res = StreamingHttpResponse(['abc']) - conditional_content_removal(req, res) - self.assertEqual(b''.join(res), b'abc') - - # Strip content for some status codes. - for status_code in (100, 150, 199, 204, 304): - res = HttpResponse('abc', status=status_code) - conditional_content_removal(req, res) - self.assertEqual(res.content, b'') - - res = StreamingHttpResponse(['abc'], status=status_code) - conditional_content_removal(req, res) - self.assertEqual(b''.join(res), b'') - - # Issue #20472 - abc = gzip_compress(b'abc') - res = HttpResponse(abc, status=304) - res['Content-Encoding'] = 'gzip' - conditional_content_removal(req, res) - self.assertEqual(res.content, b'') - - res = StreamingHttpResponse([abc], status=304) - res['Content-Encoding'] = 'gzip' - conditional_content_removal(req, res) - self.assertEqual(b''.join(res), b'') - - # Strip content for HEAD requests. - req.method = 'HEAD' - - res = HttpResponse('abc') - conditional_content_removal(req, res) - self.assertEqual(res.content, b'') - - res = StreamingHttpResponse(['abc']) - conditional_content_removal(req, res) - self.assertEqual(b''.join(res), b'') diff --git a/tests/test_client/test_conditional_content_removal.py b/tests/test_client/test_conditional_content_removal.py new file mode 100644 index 0000000000..cc5bdb69ed --- /dev/null +++ b/tests/test_client/test_conditional_content_removal.py @@ -0,0 +1,71 @@ +from __future__ import unicode_literals + +import gzip +import io + +from django.http import HttpRequest, HttpResponse, StreamingHttpResponse +from django.test import SimpleTestCase +from django.test.client import conditional_content_removal + + +# based on Python 3.3's gzip.compress +def gzip_compress(data): + buf = io.BytesIO() + f = gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=0) + try: + f.write(data) + finally: + f.close() + return buf.getvalue() + + +class ConditionalContentTests(SimpleTestCase): + + def test_conditional_content_removal(self): + """ + Content is removed from regular and streaming responses with a + status_code of 100-199, 204, 304, or a method of "HEAD". + """ + req = HttpRequest() + + # Do nothing for 200 responses. + res = HttpResponse('abc') + conditional_content_removal(req, res) + self.assertEqual(res.content, b'abc') + + res = StreamingHttpResponse(['abc']) + conditional_content_removal(req, res) + self.assertEqual(b''.join(res), b'abc') + + # Strip content for some status codes. + for status_code in (100, 150, 199, 204, 304): + res = HttpResponse('abc', status=status_code) + conditional_content_removal(req, res) + self.assertEqual(res.content, b'') + + res = StreamingHttpResponse(['abc'], status=status_code) + conditional_content_removal(req, res) + self.assertEqual(b''.join(res), b'') + + # Issue #20472 + abc = gzip_compress(b'abc') + res = HttpResponse(abc, status=304) + res['Content-Encoding'] = 'gzip' + conditional_content_removal(req, res) + self.assertEqual(res.content, b'') + + res = StreamingHttpResponse([abc], status=304) + res['Content-Encoding'] = 'gzip' + conditional_content_removal(req, res) + self.assertEqual(b''.join(res), b'') + + # Strip content for HEAD requests. + req.method = 'HEAD' + + res = HttpResponse('abc') + conditional_content_removal(req, res) + self.assertEqual(res.content, b'') + + res = StreamingHttpResponse(['abc']) + conditional_content_removal(req, res) + self.assertEqual(b''.join(res), b'') -- cgit v1.3