mirror of https://github.com/django/django.git
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.
|
# historical behavior of request_finished.
|
||||||
self._handler_class = None
|
self._handler_class = None
|
||||||
self.cookies = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
|
self.closed = False
|
||||||
if status is not None:
|
if status is not None:
|
||||||
self.status_code = status
|
self.status_code = status
|
||||||
if reason is not None:
|
if reason is not None:
|
||||||
|
@ -313,16 +314,26 @@ class HttpResponseBase(six.Iterator):
|
||||||
closable.close()
|
closable.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
self.closed = True
|
||||||
signals.request_finished.send(sender=self._handler_class)
|
signals.request_finished.send(sender=self._handler_class)
|
||||||
|
|
||||||
def write(self, content):
|
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):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tell(self):
|
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):
|
class HttpResponse(HttpResponseBase):
|
||||||
|
@ -373,6 +384,16 @@ class HttpResponse(HttpResponseBase):
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return len(self.content)
|
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):
|
class StreamingHttpResponse(HttpResponseBase):
|
||||||
"""
|
"""
|
||||||
|
@ -410,6 +431,9 @@ class StreamingHttpResponse(HttpResponseBase):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.streaming_content
|
return self.streaming_content
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
return b''.join(self.streaming_content)
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseRedirectBase(HttpResponse):
|
class HttpResponseRedirectBase(HttpResponse):
|
||||||
allowed_schemes = ['http', 'https', 'ftp']
|
allowed_schemes = ['http', 'https', 'ftp']
|
||||||
|
|
|
@ -651,6 +651,12 @@ Attributes
|
||||||
This attribute exists so middleware can treat streaming responses
|
This attribute exists so middleware can treat streaming responses
|
||||||
differently from regular responses.
|
differently from regular responses.
|
||||||
|
|
||||||
|
.. attribute:: HttpResponse.closed
|
||||||
|
|
||||||
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
``True`` if the response has been closed.
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -769,6 +775,27 @@ Methods
|
||||||
|
|
||||||
This method makes an :class:`HttpResponse` instance a file-like object.
|
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
|
.. _HTTP status code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
|
||||||
|
|
||||||
.. _ref-httpresponse-subclasses:
|
.. _ref-httpresponse-subclasses:
|
||||||
|
|
|
@ -385,6 +385,10 @@ Requests and Responses
|
||||||
<django.http.HttpRequest.get_full_path>` method now escapes unsafe characters
|
<django.http.HttpRequest.get_full_path>` method now escapes unsafe characters
|
||||||
from the path portion of a Uniform Resource Identifier (URI) properly.
|
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
|
Tests
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,15 @@ class HttpResponseTests(unittest.TestCase):
|
||||||
r.write(b'def')
|
r.write(b'def')
|
||||||
self.assertEqual(r.content, b'abcdef')
|
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):
|
def test_unsafe_redirect(self):
|
||||||
bad_urls = [
|
bad_urls = [
|
||||||
'data:text/html,<script>window.alert("xss")</script>',
|
'data:text/html,<script>window.alert("xss")</script>',
|
||||||
|
@ -537,6 +546,9 @@ class StreamingHttpResponseTests(TestCase):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
r.tell()
|
r.tell()
|
||||||
|
|
||||||
|
r = StreamingHttpResponse(iter(['hello', 'world']))
|
||||||
|
self.assertEqual(r.getvalue(), b'helloworld')
|
||||||
|
|
||||||
|
|
||||||
class FileCloseTests(TestCase):
|
class FileCloseTests(TestCase):
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,37 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.http.response import HttpResponseBase
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
UTF8 = 'utf-8'
|
UTF8 = 'utf-8'
|
||||||
ISO88591 = 'iso-8859-1'
|
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):
|
def test_status_code(self):
|
||||||
resp = HttpResponse(status=418)
|
resp = HttpResponse(status=418)
|
||||||
self.assertEqual(resp.status_code, 418)
|
self.assertEqual(resp.status_code, 418)
|
||||||
|
|
Loading…
Reference in New Issue