Fixed #16494 by normalizing HttpResponse behavior with non-string input. HttpResponse now always converts content to string on output, regardless of input type.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16829 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Paul McMillan 2011-09-14 23:27:35 +00:00
parent 67e05fcd39
commit 50255e3305
3 changed files with 65 additions and 19 deletions

View File

@ -544,12 +544,7 @@ class HttpResponse(object):
if not content_type: if not content_type:
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset) self._charset)
if not isinstance(content, basestring) and hasattr(content, '__iter__'): HttpResponse._set_content(self, content)
self._container = content
self._is_string = False
else:
self._container = [content]
self._is_string = True
self.cookies = SimpleCookie() self.cookies = SimpleCookie()
if status: if status:
self.status_code = status self.status_code = status
@ -649,12 +644,16 @@ class HttpResponse(object):
def _get_content(self): def _get_content(self):
if self.has_header('Content-Encoding'): if self.has_header('Content-Encoding'):
return ''.join(self._container) return ''.join([str(e) for e in self._container])
return smart_str(''.join(self._container), self._charset) return ''.join([smart_str(e, self._charset) for e in self._container])
def _set_content(self, value): def _set_content(self, value):
self._container = [value] if hasattr(value, '__iter__'):
self._is_string = True self._container = value
self._base_content_is_iter = True
else:
self._container = [value]
self._base_content_is_iter = False
content = property(_get_content, _set_content) content = property(_get_content, _set_content)
@ -675,7 +674,7 @@ class HttpResponse(object):
# The remaining methods partially implement the file-like object interface. # The remaining methods partially implement the file-like object interface.
# See http://docs.python.org/lib/bltin-file-objects.html # See http://docs.python.org/lib/bltin-file-objects.html
def write(self, content): def write(self, content):
if not self._is_string: if self._base_content_is_iter:
raise Exception("This %s instance is not writable" % self.__class__) raise Exception("This %s instance is not writable" % self.__class__)
self._container.append(content) self._container.append(content)
@ -683,9 +682,9 @@ class HttpResponse(object):
pass pass
def tell(self): def tell(self):
if not self._is_string: if self._base_content_is_iter:
raise Exception("This %s instance cannot tell its position" % self.__class__) raise Exception("This %s instance cannot tell its position" % self.__class__)
return sum([len(chunk) for chunk in self._container]) return sum([len(str(chunk)) for chunk in self._container])
class HttpResponseRedirect(HttpResponse): class HttpResponseRedirect(HttpResponse):
status_code = 302 status_code = 302

View File

@ -587,7 +587,7 @@ Attributes
.. attribute:: HttpResponse.content .. attribute:: HttpResponse.content
A normal Python string representing the content, encoded from a Unicode A string representing the content, encoded from a Unicode
object if necessary. object if necessary.
.. attribute:: HttpResponse.status_code .. attribute:: HttpResponse.status_code
@ -603,9 +603,11 @@ Methods
string) and MIME type. The :setting:`DEFAULT_CONTENT_TYPE` is string) and MIME type. The :setting:`DEFAULT_CONTENT_TYPE` is
``'text/html'``. ``'text/html'``.
``content`` can be an iterator or a string. If it's an iterator, it should ``content`` should be an iterator or a string. If it's an
return strings, and those strings will be joined together to form the iterator, it should return strings, and those strings will be
content of the response. joined together to form the content of the response. If it is not
an iterator or a string, it will be converted to a string when
accessed.
``status`` is the `HTTP Status code`_ for the response. ``status`` is the `HTTP Status code`_ for the response.

View File

@ -216,13 +216,13 @@ class HttpResponseTests(unittest.TestCase):
r['value'] = u'test value' r['value'] = u'test value'
self.assertTrue(isinstance(r['value'], str)) self.assertTrue(isinstance(r['value'], str))
# An error is raised ~hen a unicode object with non-ascii is assigned. # An error is raised when a unicode object with non-ascii is assigned.
self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', u't\xebst value') self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', u't\xebst value')
# An error is raised when a unicode object with non-ASCII format is # An error is raised when a unicode object with non-ASCII format is
# passed as initial mimetype or content_type. # passed as initial mimetype or content_type.
self.assertRaises(UnicodeEncodeError, HttpResponse, self.assertRaises(UnicodeEncodeError, HttpResponse,
mimetype=u't\xebst value') content_type=u't\xebst value')
# HttpResponse headers must be convertible to ASCII. # HttpResponse headers must be convertible to ASCII.
self.assertRaises(UnicodeEncodeError, HttpResponse, self.assertRaises(UnicodeEncodeError, HttpResponse,
@ -250,6 +250,51 @@ class HttpResponseTests(unittest.TestCase):
r = HttpResponse() r = HttpResponse()
self.assertEqual(r.get('test'), None) self.assertEqual(r.get('test'), None)
def test_non_string_content(self):
#Bug 16494: HttpResponse should behave consistently with non-strings
r = HttpResponse(12345)
self.assertEqual(r.content, '12345')
#test content via property
r = HttpResponse()
r.content = 12345
self.assertEqual(r.content, '12345')
def test_iter_content(self):
r = HttpResponse(['abc', 'def', 'ghi'])
self.assertEqual(r.content, 'abcdefghi')
#test iter content via property
r = HttpResponse()
r.content = ['idan', 'alex', 'jacob']
self.assertEqual(r.content, 'idanalexjacob')
r = HttpResponse()
r.content = [1, 2, 3]
self.assertEqual(r.content, '123')
#test retrieval explicitly using iter and odd inputs
r = HttpResponse()
r.content = ['1', u'2', 3, unichr(1950)]
result = []
my_iter = r.__iter__()
while True:
try:
result.append(my_iter.next())
except StopIteration:
break
#'\xde\x9e' == unichr(1950).encode('utf-8')
self.assertEqual(result, ['1', '2', '3', '\xde\x9e'])
self.assertEqual(r.content, '123\xde\x9e')
#with Content-Encoding header
r = HttpResponse([1,1,2,4,8])
r['Content-Encoding'] = 'winning'
self.assertEqual(r.content, '11248')
r.content = [unichr(1950),]
self.assertRaises(UnicodeEncodeError,
getattr, r, 'content')
class CookieTests(unittest.TestCase): class CookieTests(unittest.TestCase):
def test_encode(self): def test_encode(self):
""" """