Fixed #18523 -- Added stream-like API to HttpResponse.
Added getvalue() to HttpResponse to return the content of the response, along with a few other methods to partially match io.IOBase. Thanks Claude Paroz for the suggestion and Nick Sanford for review.
This commit is contained in:
parent
f7969b0920
commit
ebc8e79cf3
|
@ -112,6 +112,7 @@ class HttpResponseBase(six.Iterator):
|
|||
# historical behavior of request_finished.
|
||||
self._handler_class = None
|
||||
self.cookies = SimpleCookie()
|
||||
self.closed = False
|
||||
if status is not None:
|
||||
self.status_code = status
|
||||
if reason is not None:
|
||||
|
@ -313,16 +314,26 @@ class HttpResponseBase(six.Iterator):
|
|||
closable.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.closed = True
|
||||
signals.request_finished.send(sender=self._handler_class)
|
||||
|
||||
def write(self, content):
|
||||
raise Exception("This %s instance is not writable" % self.__class__.__name__)
|
||||
raise IOError("This %s instance is not writable" % self.__class__.__name__)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def tell(self):
|
||||
raise Exception("This %s instance cannot tell its position" % self.__class__.__name__)
|
||||
raise IOError("This %s instance cannot tell its position" % self.__class__.__name__)
|
||||
|
||||
# These methods partially implement a stream-like object interface.
|
||||
# See https://docs.python.org/library/io.html#io.IOBase
|
||||
|
||||
def writable(self):
|
||||
return False
|
||||
|
||||
def writelines(self, lines):
|
||||
raise IOError("This %s instance is not writable" % self.__class__.__name__)
|
||||
|
||||
|
||||
class HttpResponse(HttpResponseBase):
|
||||
|
@ -373,6 +384,16 @@ class HttpResponse(HttpResponseBase):
|
|||
def tell(self):
|
||||
return len(self.content)
|
||||
|
||||
def getvalue(self):
|
||||
return self.content
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
|
||||
class StreamingHttpResponse(HttpResponseBase):
|
||||
"""
|
||||
|
@ -410,6 +431,9 @@ class StreamingHttpResponse(HttpResponseBase):
|
|||
def __iter__(self):
|
||||
return self.streaming_content
|
||||
|
||||
def getvalue(self):
|
||||
return b''.join(self.streaming_content)
|
||||
|
||||
|
||||
class HttpResponseRedirectBase(HttpResponse):
|
||||
allowed_schemes = ['http', 'https', 'ftp']
|
||||
|
|
|
@ -651,6 +651,12 @@ Attributes
|
|||
This attribute exists so middleware can treat streaming responses
|
||||
differently from regular responses.
|
||||
|
||||
.. attribute:: HttpResponse.closed
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
``True`` if the response has been closed.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
|
@ -769,6 +775,27 @@ Methods
|
|||
|
||||
This method makes an :class:`HttpResponse` instance a file-like object.
|
||||
|
||||
.. method:: HttpResponse.getvalue()
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
Returns the value of :attr:`HttpResponse.content`. This method makes
|
||||
an :class:`HttpResponse` instance a stream-like object.
|
||||
|
||||
.. method:: HttpResponse.writable()
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
Always ``True``. This method makes an :class:`HttpResponse` instance a
|
||||
stream-like object.
|
||||
|
||||
.. method:: HttpResponse.writelines(lines)
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
Writes a list of lines to the response. Line seperators are not added. This
|
||||
method makes an :class:`HttpResponse` instance a stream-like object.
|
||||
|
||||
.. _HTTP status code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
|
||||
|
||||
.. _ref-httpresponse-subclasses:
|
||||
|
|
|
@ -385,6 +385,10 @@ Requests and Responses
|
|||
<django.http.HttpRequest.get_full_path>` method now escapes unsafe characters
|
||||
from the path portion of a Uniform Resource Identifier (URI) properly.
|
||||
|
||||
* :class:`~django.http.HttpResponse` now implements a few additional methods
|
||||
like :meth:`~django.http.HttpResponse.getvalue` so that instances can be used
|
||||
as stream objects.
|
||||
|
||||
Tests
|
||||
^^^^^
|
||||
|
||||
|
|
|
@ -407,6 +407,15 @@ class HttpResponseTests(unittest.TestCase):
|
|||
r.write(b'def')
|
||||
self.assertEqual(r.content, b'abcdef')
|
||||
|
||||
def test_stream_interface(self):
|
||||
r = HttpResponse('asdf')
|
||||
self.assertEqual(r.getvalue(), b'asdf')
|
||||
|
||||
r = HttpResponse()
|
||||
self.assertEqual(r.writable(), True)
|
||||
r.writelines(['foo\n', 'bar\n', 'baz\n'])
|
||||
self.assertEqual(r.content, b'foo\nbar\nbaz\n')
|
||||
|
||||
def test_unsafe_redirect(self):
|
||||
bad_urls = [
|
||||
'data:text/html,<script>window.alert("xss")</script>',
|
||||
|
@ -537,6 +546,9 @@ class StreamingHttpResponseTests(TestCase):
|
|||
with self.assertRaises(Exception):
|
||||
r.tell()
|
||||
|
||||
r = StreamingHttpResponse(iter(['hello', 'world']))
|
||||
self.assertEqual(r.getvalue(), b'helloworld')
|
||||
|
||||
|
||||
class FileCloseTests(TestCase):
|
||||
|
||||
|
|
|
@ -4,14 +4,37 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
UTF8 = 'utf-8'
|
||||
ISO88591 = 'iso-8859-1'
|
||||
|
||||
|
||||
class HttpResponseTests(SimpleTestCase):
|
||||
class HttpResponseBaseTests(SimpleTestCase):
|
||||
def test_closed(self):
|
||||
r = HttpResponseBase()
|
||||
self.assertIs(r.closed, False)
|
||||
|
||||
r.close()
|
||||
self.assertIs(r.closed, True)
|
||||
|
||||
def test_write(self):
|
||||
r = HttpResponseBase()
|
||||
self.assertIs(r.writable(), False)
|
||||
|
||||
with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance is not writable'):
|
||||
r.write('asdf')
|
||||
with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance is not writable'):
|
||||
r.writelines(['asdf\n', 'qwer\n'])
|
||||
|
||||
def test_tell(self):
|
||||
r = HttpResponseBase()
|
||||
with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance cannot tell its position'):
|
||||
r.tell()
|
||||
|
||||
|
||||
class HttpResponseTests(SimpleTestCase):
|
||||
def test_status_code(self):
|
||||
resp = HttpResponse(status=418)
|
||||
self.assertEqual(resp.status_code, 418)
|
||||
|
|
Loading…
Reference in New Issue