Fixed #25725 -- Made HttpReponse immediately close objects.

This commit is contained in:
Johannes Hoppe 2015-11-11 20:17:32 +01:00 committed by Tim Graham
parent a6c803a2e3
commit 5233b70070
5 changed files with 29 additions and 18 deletions

View File

@ -316,13 +316,16 @@ class HttpResponse(HttpResponseBase):
def content(self, value): def content(self, value):
# Consume iterators upon assignment to allow repeated iteration. # Consume iterators upon assignment to allow repeated iteration.
if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)): if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)):
content = b''.join(self.make_bytes(chunk) for chunk in value)
if hasattr(value, 'close'): if hasattr(value, 'close'):
self._closable_objects.append(value) try:
value = b''.join(self.make_bytes(chunk) for chunk in value) value.close()
except Exception:
pass
else: else:
value = self.make_bytes(value) content = self.make_bytes(value)
# Create a list of properly encoded bytestrings to support write(). # Create a list of properly encoded bytestrings to support write().
self._container = [value] self._container = [content]
def __iter__(self): def __iter__(self):
return iter(self._container) return iter(self._container)

View File

@ -608,11 +608,17 @@ Passing iterators
Finally, you can pass ``HttpResponse`` an iterator rather than strings. Finally, you can pass ``HttpResponse`` an iterator rather than strings.
``HttpResponse`` will consume the iterator immediately, store its content as a ``HttpResponse`` will consume the iterator immediately, store its content as a
string, and discard it. string, and discard it. Objects with a ``close()`` method such as files and
generators are immediately closed.
If you need the response to be streamed from the iterator to the client, you If you need the response to be streamed from the iterator to the client, you
must use the :class:`StreamingHttpResponse` class instead. must use the :class:`StreamingHttpResponse` class instead.
.. versionchanged:: 1.10
Objects with a ``close()`` method used to be closed when the WSGI server
called ``close()`` on the response.
Setting header fields Setting header fields
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

View File

@ -357,6 +357,10 @@ Miscellaneous
* The ``add_postgis_srs()`` backwards compatibility alias for * The ``add_postgis_srs()`` backwards compatibility alias for
``django.contrib.gis.utils.add_srs_entry()`` is removed. ``django.contrib.gis.utils.add_srs_entry()`` is removed.
* Objects with a ``close()`` method such as files and generators passed to
:class:`~django.http.HttpResponse` are now closed immediately instead of when
the WSGI server calls ``close()`` on the response.
.. _deprecated-features-1.10: .. _deprecated-features-1.10:
Features deprecated in 1.10 Features deprecated in 1.10

View File

@ -588,18 +588,8 @@ class FileCloseTests(SimpleTestCase):
# file isn't closed until we close the response. # file isn't closed until we close the response.
file1 = open(filename) file1 = open(filename)
r = HttpResponse(file1) r = HttpResponse(file1)
self.assertFalse(file1.closed)
r.close()
self.assertTrue(file1.closed) self.assertTrue(file1.closed)
# don't automatically close file when we finish iterating the response.
file1 = open(filename)
r = HttpResponse(file1)
self.assertFalse(file1.closed)
list(r)
self.assertFalse(file1.closed)
r.close() r.close()
self.assertTrue(file1.closed)
# when multiple file are assigned as content, make sure they are all # when multiple file are assigned as content, make sure they are all
# closed with the response. # closed with the response.
@ -607,9 +597,6 @@ class FileCloseTests(SimpleTestCase):
file2 = open(filename) file2 = open(filename)
r = HttpResponse(file1) r = HttpResponse(file1)
r.content = file2 r.content = file2
self.assertFalse(file1.closed)
self.assertFalse(file2.closed)
r.close()
self.assertTrue(file1.closed) self.assertTrue(file1.closed)
self.assertTrue(file2.closed) self.assertTrue(file2.closed)

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import io import io
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse from django.http import HttpResponse
from django.http.response import HttpResponseBase from django.http.response import HttpResponseBase
from django.test import SimpleTestCase from django.test import SimpleTestCase
@ -121,3 +122,13 @@ class HttpResponseTests(SimpleTestCase):
with io.TextIOWrapper(r, UTF8) as buf: with io.TextIOWrapper(r, UTF8) as buf:
buf.write(content) buf.write(content)
self.assertEqual(r.content, content.encode(UTF8)) self.assertEqual(r.content, content.encode(UTF8))
def test_generator_cache(self):
generator = ("{}".format(i) for i in range(10))
response = HttpResponse(content=generator)
self.assertEqual(response.content, b'0123456789')
self.assertRaises(StopIteration, next, generator)
cache.set('my-response-key', response)
response = cache.get('my-response-key')
self.assertEqual(response.content, b'0123456789')