diff --git a/django/middleware/common.py b/django/middleware/common.py index 8cb00ff250..b705f72b81 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -123,6 +123,10 @@ class CommonMiddleware(MiddlewareMixin): etag=unquote_etag(response['ETag']), response=response, ) + # Add the Content-Length header to non-streaming responses if not + # already set. + if not response.streaming and not response.has_header('Content-Length'): + response['Content-Length'] = str(len(response.content)) return response diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 8b991d5ed8..371121c30e 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -66,6 +66,12 @@ Adds a few conveniences for perfectionists: for each request by MD5-hashing the page content, and it'll take care of sending ``Not Modified`` responses, if appropriate. +* Sets the ``Content-Length`` header for non-streaming responses. + +.. versionchanged: 1.11 + + Older versions didn't set the ``Content-Length`` header. + .. attribute:: CommonMiddleware.response_redirect_class Defaults to :class:`~django.http.HttpResponsePermanentRedirect`. Subclass diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index e5dcc47e24..23af4a4d60 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -197,6 +197,9 @@ Requests and Responses * Added :meth:`QueryDict.fromkeys() `. +* :class:`~django.middleware.common.CommonMiddleware` now sets the + ``Content-Length`` response header for non-streaming responses. + Serialization ~~~~~~~~~~~~~ diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index f87bb9d71c..f9ef951b44 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -285,6 +285,27 @@ class CommonMiddlewareTest(SimpleTestCase): second_res = CommonMiddleware().process_response(second_req, HttpResponse('content')) self.assertEqual(second_res.status_code, 304) + # Tests for the Content-Length header + + def test_content_length_header_added(self): + response = HttpResponse('content') + self.assertNotIn('Content-Length', response) + response = CommonMiddleware().process_response(HttpRequest(), response) + self.assertEqual(int(response['Content-Length']), len(response.content)) + + def test_content_length_header_not_added_for_streaming_response(self): + response = StreamingHttpResponse('content') + self.assertNotIn('Content-Length', response) + response = CommonMiddleware().process_response(HttpRequest(), response) + self.assertNotIn('Content-Length', response) + + def test_content_length_header_not_changed(self): + response = HttpResponse() + bad_content_length = len(response.content) + 10 + response['Content-Length'] = bad_content_length + response = CommonMiddleware().process_response(HttpRequest(), response) + self.assertEqual(int(response['Content-Length']), bad_content_length) + # Other tests @override_settings(DISALLOWED_USER_AGENTS=[re.compile(r'foo')]) @@ -445,6 +466,9 @@ class ConditionalGetMiddlewareTest(SimpleTestCase): def test_content_length_header_added(self): content_length = len(self.resp.content) + # Already set by CommonMiddleware, remove it to check that + # ConditionalGetMiddleware readds it. + del self.resp['Content-Length'] self.assertNotIn('Content-Length', self.resp) self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) self.assertIn('Content-Length', self.resp) diff --git a/tests/project_template/test_settings.py b/tests/project_template/test_settings.py index d153c4d95f..a0047dd836 100644 --- a/tests/project_template/test_settings.py +++ b/tests/project_template/test_settings.py @@ -41,6 +41,7 @@ class TestStartProjectSettings(TestCase): response = self.client.get('/empty/') headers = sorted(response.serialize_headers().split(b'\r\n')) self.assertEqual(headers, [ + b'Content-Length: 0', b'Content-Type: text/html; charset=utf-8', b'X-Frame-Options: SAMEORIGIN', ]) diff --git a/tests/wsgi/tests.py b/tests/wsgi/tests.py index f472d4fab9..03ac7ee706 100644 --- a/tests/wsgi/tests.py +++ b/tests/wsgi/tests.py @@ -41,11 +41,12 @@ class WSGITest(SimpleTestCase): self.assertEqual(response_data["status"], "200 OK") self.assertEqual( - response_data["headers"], - [('Content-Type', 'text/html; charset=utf-8')]) - self.assertEqual( - bytes(response), - b"Content-Type: text/html; charset=utf-8\r\n\r\nHello World!") + set(response_data["headers"]), + {('Content-Length', '12'), ('Content-Type', 'text/html; charset=utf-8')}) + self.assertTrue(bytes(response) in [ + b"Content-Length: 12\r\nContent-Type: text/html; charset=utf-8\r\n\r\nHello World!", + b"Content-Type: text/html; charset=utf-8\r\nContent-Length: 12\r\n\r\nHello World!" + ]) def test_file_wrapper(self): """