Ticket #7581: 7581-20121020.patch
File 7581-20121020.patch, 35.9 KB (added by , 12 years ago) |
---|
-
django/http/__init__.py
diff --git a/django/http/__init__.py b/django/http/__init__.py index b385b45..11cd5e9 100644
a b def parse_cookie(cookie): 526 526 class BadHeaderError(ValueError): 527 527 pass 528 528 529 class HttpResponse(object): 530 """A basic HTTP response, with content and dictionary-accessed headers.""" 529 class HttpResponseBase(object): 530 """ 531 An HTTP response base class with dictionary-accessed headers. 532 533 This class doesn't handle content. It should not be used directly. 534 Use the HttpResponse and StreamingHttpResponse subclasses instead. 535 """ 531 536 532 537 status_code = 200 533 538 534 def __init__(self, content='', content_type=None, status=None, 535 mimetype=None): 539 def __init__(self, content_type=None, status=None, mimetype=None): 536 540 # _headers is a mapping of the lower-case name to the original case of 537 541 # the header (required for working with legacy systems) and the header 538 542 # value. Both the name of the header and its value are ASCII strings. 539 543 self._headers = {} 540 544 self._charset = settings.DEFAULT_CHARSET 545 self._closable_objects = [] 541 546 if mimetype: 542 547 warnings.warn("Using mimetype keyword argument is deprecated, use" 543 548 " content_type instead", PendingDeprecationWarning) … … class HttpResponse(object): 545 550 if not content_type: 546 551 content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, 547 552 self._charset) 548 # content is a bytestring. See the content property methods.549 self.content = content550 553 self.cookies = SimpleCookie() 551 554 if status: 552 555 self.status_code = status 553 556 554 557 self['Content-Type'] = content_type 555 558 556 def serialize (self):557 """ Full HTTP message, including headers,as a bytestring."""559 def serialize_headers(self): 560 """HTTP headers as a bytestring.""" 558 561 headers = [ 559 562 ('%s: %s' % (key, value)).encode('us-ascii') 560 563 for key, value in self._headers.values() 561 564 ] 562 return b'\r\n'.join(headers) + b'\r\n\r\n' + self.content565 return b'\r\n'.join(headers) 563 566 564 567 if six.PY3: 565 __bytes__ = serialize 568 __bytes__ = serialize_headers 566 569 else: 567 __str__ = serialize 570 __str__ = serialize_headers 568 571 569 572 def _convert_to_charset(self, value, charset, mime_encode=False): 570 573 """Converts headers key/value to ascii/latin1 native strings. … … class HttpResponse(object): 688 691 self.set_cookie(key, max_age=0, path=path, domain=domain, 689 692 expires='Thu, 01-Jan-1970 00:00:00 GMT') 690 693 694 # Common methods used by subclasses 695 696 def make_bytes(self, value): 697 """Turn a value into a bytestring encoded in the output charset.""" 698 # For backwards compatibility, this method supports values that are 699 # unlikely to occur in real applications. It has grown complex and 700 # should be refactored. It also overlaps __next__. See #18796. 701 if self.has_header('Content-Encoding'): 702 if isinstance(value, int): 703 value = six.text_type(value) 704 if isinstance(value, six.text_type): 705 value = value.encode('ascii') 706 # force conversion to bytes in case chunk is a subclass 707 return bytes(value) 708 else: 709 return force_bytes(value, self._charset) 710 711 # These methods partially implement the file-like object interface. 712 # See http://docs.python.org/lib/bltin-file-objects.html 713 714 def close(self): 715 for closable in self._closable_objects: 716 closable.close() 717 718 def write(self, content): 719 raise Exception("This %s instance is not writable" % self.__class__.__name__) 720 721 def flush(self): 722 pass 723 724 def tell(self): 725 raise Exception("This %s instance cannot tell its position" % self.__class__.__name__) 726 727 class HttpResponse(HttpResponseBase): 728 """ 729 An HTTP response class with a string as content. 730 731 This content that can be read, appended to or replaced. 732 """ 733 734 streaming = False 735 736 def __init__(self, content='', *args, **kwargs): 737 super(HttpResponse, self).__init__(*args, **kwargs) 738 # Content is a bytestring. See the `content` property methods. 739 self.content = content 740 741 def serialize(self): 742 """Full HTTP message, including headers, as a bytestring.""" 743 return self.serialize_headers() + b'\r\n\r\n' + self.content 744 745 if six.PY3: 746 __bytes__ = serialize 747 else: 748 __str__ = serialize 749 691 750 @property 692 751 def content(self): 693 if self.has_header('Content-Encoding'): 694 def make_bytes(value): 695 if isinstance(value, int): 696 value = six.text_type(value) 697 if isinstance(value, six.text_type): 698 value = value.encode('ascii') 699 # force conversion to bytes in case chunk is a subclass 700 return bytes(value) 701 return b''.join(make_bytes(e) for e in self._container) 702 return b''.join(force_bytes(e, self._charset) for e in self._container) 752 return b''.join(self.make_bytes(e) for e in self._container) 703 753 704 754 @content.setter 705 755 def content(self, value): 706 756 if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)): 707 757 self._container = value 708 758 self._base_content_is_iter = True 759 if hasattr(value, 'close'): 760 self._closable_objects.append(value) 709 761 else: 710 762 self._container = [value] 711 763 self._base_content_is_iter = False … … class HttpResponse(object): 725 777 726 778 next = __next__ # Python 2 compatibility 727 779 728 def close(self):729 if hasattr(self._container, 'close'):730 self._container.close()731 732 # The remaining methods partially implement the file-like object interface.733 # See http://docs.python.org/lib/bltin-file-objects.html734 780 def write(self, content): 735 781 if self._base_content_is_iter: 736 raise Exception("This %s instance is not writable" % self.__class__ )782 raise Exception("This %s instance is not writable" % self.__class__.__name__) 737 783 self._container.append(content) 738 784 739 def flush(self):740 pass741 742 785 def tell(self): 743 786 if self._base_content_is_iter: 744 raise Exception("This %s instance cannot tell its position" % self.__class__ )787 raise Exception("This %s instance cannot tell its position" % self.__class__.__name__) 745 788 return sum([len(chunk) for chunk in self]) 746 789 790 class StreamingHttpResponse(HttpResponseBase): 791 """ 792 A streaming HTTP response class with an iterator as content. 793 794 This should only be iterated once, when the response is streamed to the 795 client. However, it can be appended to or replaced with a new iterator 796 that wraps the original content (or yields entirely new content). 797 """ 798 799 streaming = True 800 801 def __init__(self, streaming_content=(), *args, **kwargs): 802 super(StreamingHttpResponse, self).__init__(*args, **kwargs) 803 # `streaming_content` should be an iterable of bytestrings. 804 # See the `streaming_content` property methods. 805 self.streaming_content = streaming_content 806 807 @property 808 def content(self): 809 raise AttributeError("This %s instance has no `content` attribute. " 810 "Use `streaming_content` instead." % self.__class__.__name__) 811 812 @property 813 def streaming_content(self): 814 return (self.make_bytes(chunk) for chunk in self._iterator) 815 816 @streaming_content.setter 817 def streaming_content(self, value): 818 # Ensure we can never iterate on "value" more than once. 819 self._iterator = iter(value) 820 if hasattr(value, 'close'): 821 self._closable_objects.append(value) 822 823 def __iter__(self): 824 return self 825 826 def __next__(self): 827 try: 828 chunk = next(self._iterator) 829 except StopIteration: 830 self.close() 831 raise 832 return self.make_bytes(chunk) 833 834 next = __next__ # Python 2 compatibility 835 836 class CompatibleStreamingHttpResponse(StreamingHttpResponse): 837 """ 838 This class maintains compatibility with middleware that doesn't know how 839 to handle the content of a streaming response by exposing a `content` 840 attribute that will consume and cache the content iterator when accessed. 841 842 These responses will stream only if no middleware attempts to access the 843 `content` attribute. Otherwise, they will behave like a regular response, 844 and raise a `PendingDeprecationWarning`. 845 """ 846 @property 847 def content(self): 848 warnings.warn( 849 'Accessing the `content` attribute on a streaming response is ' 850 'deprecated. Use the `streaming_content` attribute instead.', 851 PendingDeprecationWarning) 852 content = b''.join(self) 853 self.streaming_content = [content] 854 return content 855 856 @content.setter 857 def content(self, content): 858 warnings.warn( 859 'Accessing the `content` attribute on a streaming response is ' 860 'deprecated. Use the `streaming_content` attribute instead.', 861 PendingDeprecationWarning) 862 self.streaming_content = [content] 863 747 864 class HttpResponseRedirectBase(HttpResponse): 748 865 allowed_schemes = ['http', 'https', 'ftp'] 749 866 -
django/http/utils.py
diff --git a/django/http/utils.py b/django/http/utils.py index 0180864..fe42ecd 100644
a b def conditional_content_removal(request, response): 26 26 responses. Ensures compliance with RFC 2616, section 4.3. 27 27 """ 28 28 if 100 <= response.status_code < 200 or response.status_code in (204, 304): 29 response.content = '' 30 response['Content-Length'] = 0 29 if response.streaming: 30 response.streaming_content = [] 31 else: 32 response.content = '' 33 response['Content-Length'] = 0 31 34 if request.method == 'HEAD': 32 response.content = '' 35 if response.streaming: 36 response.streaming_content = [] 37 else: 38 response.content = '' 33 39 return response 34 40 35 41 def fix_IE_for_attach(request, response): -
django/middleware/common.py
diff --git a/django/middleware/common.py b/django/middleware/common.py index 0ec17fb..6fbbf43 100644
a b class CommonMiddleware(object): 113 113 if settings.USE_ETAGS: 114 114 if response.has_header('ETag'): 115 115 etag = response['ETag'] 116 elif response.streaming: 117 etag = None 116 118 else: 117 119 etag = '"%s"' % hashlib.md5(response.content).hexdigest() 118 if response.status_code >= 200 and response.status_code < 300 and request.META.get('HTTP_IF_NONE_MATCH') == etag: 119 cookies = response.cookies 120 response = http.HttpResponseNotModified() 121 response.cookies = cookies 122 else: 123 response['ETag'] = etag 120 if etag is not None: 121 if (200 <= response.status_code < 300 122 and request.META.get('HTTP_IF_NONE_MATCH') == etag): 123 cookies = response.cookies 124 response = http.HttpResponseNotModified() 125 response.cookies = cookies 126 else: 127 response['ETag'] = etag 124 128 125 129 return response 126 130 -
django/middleware/gzip.py
diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py index 69f938c..fb54501 100644
a b 1 1 import re 2 2 3 from django.utils.text import compress_s tring3 from django.utils.text import compress_sequence, compress_string 4 4 from django.utils.cache import patch_vary_headers 5 5 6 6 re_accepts_gzip = re.compile(r'\bgzip\b') … … class GZipMiddleware(object): 13 13 """ 14 14 def process_response(self, request, response): 15 15 # It's not worth attempting to compress really short responses. 16 if len(response.content) < 200:16 if not response.streaming and len(response.content) < 200: 17 17 return response 18 18 19 19 patch_vary_headers(response, ('Accept-Encoding',)) … … class GZipMiddleware(object): 32 32 if not re_accepts_gzip.search(ae): 33 33 return response 34 34 35 # Return the compressed content only if it's actually shorter. 36 compressed_content = compress_string(response.content) 37 if len(compressed_content) >= len(response.content): 38 return response 35 if response.streaming: 36 # Delete the `Content-Length` header for streaming content, because 37 # we won't know the compressed size until we stream it. 38 response.streaming_content = compress_sequence(response.streaming_content) 39 del response['Content-Length'] 40 else: 41 # Return the compressed content only if it's actually shorter. 42 compressed_content = compress_string(response.content) 43 if len(compressed_content) >= len(response.content): 44 return response 45 response.content = compressed_content 46 response['Content-Length'] = str(len(response.content)) 39 47 40 48 if response.has_header('ETag'): 41 49 response['ETag'] = re.sub('"$', ';gzip"', response['ETag']) 42 43 response.content = compressed_content44 50 response['Content-Encoding'] = 'gzip' 45 response['Content-Length'] = str(len(response.content)) 51 46 52 return response -
django/middleware/http.py
diff --git a/django/middleware/http.py b/django/middleware/http.py index 86e46ce..5a46e04 100644
a b class ConditionalGetMiddleware(object): 10 10 """ 11 11 def process_response(self, request, response): 12 12 response['Date'] = http_date() 13 if not response. has_header('Content-Length'):13 if not response.streaming and not response.has_header('Content-Length'): 14 14 response['Content-Length'] = str(len(response.content)) 15 15 16 16 if response.has_header('ETag'): -
django/test/testcases.py
diff --git a/django/test/testcases.py b/django/test/testcases.py index 1d52fed..4c5eb5d 100644
a b class TransactionTestCase(SimpleTestCase): 596 596 msg_prefix + "Couldn't retrieve content: Response code was %d" 597 597 " (expected %d)" % (response.status_code, status_code)) 598 598 text = force_text(text, encoding=response._charset) 599 content = response.content.decode(response._charset) 599 if response.streaming: 600 content = b''.join(response.streaming_content) 601 else: 602 content = response.content 603 content = content.decode(response._charset) 600 604 if html: 601 605 content = assert_and_parse_html(self, content, None, 602 606 "Response's content is not valid HTML:") -
django/utils/cache.py
diff --git a/django/utils/cache.py b/django/utils/cache.py index 91c4796..0fceaa9 100644
a b def get_max_age(response): 95 95 pass 96 96 97 97 def _set_response_etag(response): 98 response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest() 98 if not response.streaming: 99 response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest() 99 100 return response 100 101 101 102 def patch_response_headers(response, cache_timeout=None): -
django/utils/text.py
diff --git a/django/utils/text.py b/django/utils/text.py index c197084..d75ca8d 100644
a b def compress_string(s): 288 288 zfile.close() 289 289 return zbuf.getvalue() 290 290 291 class StreamingBuffer(object): 292 def __init__(self): 293 self.vals = [] 294 295 def write(self, val): 296 self.vals.append(val) 297 298 def read(self): 299 ret = b''.join(self.vals) 300 self.vals = [] 301 return ret 302 303 def flush(self): 304 return 305 306 def close(self): 307 return 308 309 # Like compress_string, but for iterators of strings. 310 def compress_sequence(sequence): 311 buf = StreamingBuffer() 312 zfile = GzipFile(mode='wb', compresslevel=6, fileobj=buf) 313 # Output headers... 314 yield buf.read() 315 for item in sequence: 316 zfile.write(item) 317 zfile.flush() 318 yield buf.read() 319 zfile.close() 320 yield buf.read() 321 291 322 ustring_re = re.compile("([\u0080-\uffff])") 292 323 293 324 def javascript_quote(s, quote_double_quotes=False): -
django/views/static.py
diff --git a/django/views/static.py b/django/views/static.py index 7dd44c5..f61ba28 100644
a b try: 14 14 except ImportError: # Python 2 15 15 from urllib import unquote 16 16 17 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified 17 from django.http import (CompatibleStreamingHttpResponse, Http404, 18 HttpResponse, HttpResponseRedirect, HttpResponseNotModified) 18 19 from django.template import loader, Template, Context, TemplateDoesNotExist 19 20 from django.utils.http import http_date, parse_http_date 20 21 from django.utils.translation import ugettext as _, ugettext_noop … … def serve(request, path, document_root=None, show_indexes=False): 62 63 if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), 63 64 statobj.st_mtime, statobj.st_size): 64 65 return HttpResponseNotModified() 65 with open(fullpath, 'rb') as f: 66 response = HttpResponse(f.read(), content_type=mimetype) 66 response = CompatibleStreamingHttpResponse(open(fullpath, 'rb'), content_type=mimetype) 67 67 response["Last-Modified"] = http_date(statobj.st_mtime) 68 68 if stat.S_ISREG(statobj.st_mode): 69 69 response["Content-Length"] = statobj.st_size -
docs/ref/request-response.txt
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 0a337eb..e95b13a 100644
a b file-like object:: 560 560 Passing iterators 561 561 ~~~~~~~~~~~~~~~~~ 562 562 563 Finally, you can pass ``HttpResponse`` an iterator rather than passing it564 hard-coded strings. If you use this technique, follow these guidelines: 563 Finally, you can pass ``HttpResponse`` an iterator rather than strings. If you 564 use this technique, the iterator should return strings. 565 565 566 * The iterator should return strings. 567 * If an :class:`HttpResponse` has been initialized with an iterator as its 568 content, you can't use the :class:`HttpResponse` instance as a file-like 569 object. Doing so will raise ``Exception``. 566 .. versionchanged:: 1.5 567 568 Passing an iterator as content to :class:`HttpResponse` creates a 569 streaming response if (and only if) no middleware accesses the 570 :attr:`HttpResponse.content` attribute before the response is returned. 571 572 If you want to guarantee that your response will stream to the client, you 573 should use the new :class:`StreamingHttpResponse` class instead. 574 575 If an :class:`HttpResponse` instance has been initialized with an iterator as 576 its content, you can't use it as a file-like object. Doing so will raise an 577 exception. 570 578 571 579 Setting headers 572 580 ~~~~~~~~~~~~~~~ … … Attributes 608 616 609 617 The `HTTP Status code`_ for the response. 610 618 619 .. attribute:: HttpResponse.streaming 620 621 This is always ``False``. 622 623 This attribute exists so middleware can treat streaming responses 624 differently from regular responses. 625 611 626 Methods 612 627 ------- 613 628 … … types of HTTP responses. Like ``HttpResponse``, these subclasses live in 775 790 method, Django will treat it as emulating a 776 791 :class:`~django.template.response.SimpleTemplateResponse`, and the 777 792 ``render`` method must itself return a valid response object. 793 794 StreamingHttpResponse objects 795 ============================= 796 797 .. versionadded:: 1.5 798 799 .. class:: StreamingHttpResponse 800 801 The :class:`StreamingHttpResponse` class is used to stream a response from 802 Django to the browser. You might want to do this if generating the response 803 takes too long or uses too much memory. For instance, it's useful for 804 generating large CSV files. 805 806 .. admonition:: Performance considerations 807 808 Django is designed for short-lived requests. Streaming responses will tie 809 a worker process and keep a database connection idle in transaction for 810 the entire duration of the response. This may result in poor performance. 811 812 Generally speaking, you should perform expensive tasks outside of the 813 request-response cycle, rather than resorting to a streamed response. 814 815 The :class:`StreamingHttpResponse` is not a subclass of :class:`HttpResponse`, 816 because it features a slightly different API. However, it is almost identical, 817 with the following notable differences: 818 819 * It should be given an iterator that yields strings as content. 820 821 * You cannot access its content, except by iterating the response object 822 itself. This should only occur when the response is returned to the client. 823 824 * It has no ``content`` attribute. Instead, it has a 825 :attr:`~StreamingHttpResponse.streaming_content` attribute. 826 827 * You cannot use the file-like object ``tell()`` or ``write()`` methods. 828 Doing so will raise an exception. 829 830 * Any iterators that have a ``close()`` method and are assigned as content will 831 be closed automatically after the response has been iterated. 832 833 :class:`StreamingHttpResponse` should only be used in situations where it is 834 absolutely required that the whole content isn't iterated before transferring 835 the data to the client. Because the content can't be accessed, many 836 middlewares can't function normally. For example the ``ETag`` and ``Content- 837 Length`` headers can't be generated for streaming responses. 838 839 Attributes 840 ---------- 841 842 .. attribute:: StreamingHttpResponse.streaming_content 843 844 An iterator of strings representing the content. 845 846 .. attribute:: HttpResponse.status_code 847 848 The `HTTP Status code`_ for the response. 849 850 .. attribute:: HttpResponse.streaming 851 852 This is always ``True``. -
docs/releases/1.5.txt
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index d49bae8..bec9d96 100644
a b For one-to-one relationships, both sides can be cached. For many-to-one 84 84 relationships, only the single side of the relationship can be cached. This 85 85 is particularly helpful in combination with ``prefetch_related``. 86 86 87 Explicit support for streaming responses 88 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 90 Before Django 1.5, it was possible to create a streaming response by passing 91 an iterator to :class:`~django.http.HttpResponse`. But this was unreliable: 92 any middleware that accessed the :attr:`~django.http.HttpResponse.content` 93 attribute would consume the iterator prematurely. 94 95 You can now explicitly generate a streaming response with the new 96 :class:`~django.http.StreamingHttpResponse` class. This class exposes a 97 :class:`~django.http.StreamingHttpResponse.streaming_content` attribute which 98 is an iterator. 99 100 Since :class:`~django.http.StreamingHttpResponse` does not have a ``content`` 101 attribute, middleware that need access to the response content must test for 102 streaming responses and behave accordingly. See :ref:`response-middleware` for 103 more information. 104 87 105 ``{% verbatim %}`` template tag 88 106 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 107 -
docs/topics/http/middleware.txt
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index a8347e5..c27e7e8 100644
a b an earlier middleware method returned an :class:`~django.http.HttpResponse` 164 164 classes are applied in reverse order, from the bottom up. This means classes 165 165 defined at the end of :setting:`MIDDLEWARE_CLASSES` will be run first. 166 166 167 .. versionchanged:: 1.5 168 ``response`` may also be an :class:`~django.http.StreamingHttpResponse` 169 object. 170 171 Unlike :class:`~django.http.HttpResponse`, 172 :class:`~django.http.StreamingHttpResponse` does not have a ``content`` 173 attribute. As a result, middleware can no longer assume that all responses 174 will have a ``content`` attribute. If they need access to the content, they 175 must test for streaming responses and adjust their behavior accordingly:: 176 177 if response.streaming: 178 response.streaming_content = wrap_streaming_content(response.streaming_content) 179 else: 180 response.content = wrap_content(response.content) 181 182 ``streaming_content`` should be assumed to be too large to hold in memory. 183 Middleware may wrap it in a new generator, but must not consume it. 167 184 168 185 .. _exception-middleware: 169 186 -
tests/regressiontests/cache/tests.py
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index de27bc9..a6eff89 100644
a b from django.core.cache import get_cache 19 19 from django.core.cache.backends.base import (CacheKeyWarning, 20 20 InvalidCacheBackendError) 21 21 from django.db import router 22 from django.http import HttpResponse, HttpRequest, QueryDict 22 from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, 23 QueryDict) 23 24 from django.middleware.cache import (FetchFromCacheMiddleware, 24 25 UpdateCacheMiddleware, CacheMiddleware) 25 26 from django.template import Template … … class CacheI18nTest(TestCase): 1416 1417 # reset the language 1417 1418 translation.deactivate() 1418 1419 1420 @override_settings( 1421 CACHE_MIDDLEWARE_KEY_PREFIX="test", 1422 CACHE_MIDDLEWARE_SECONDS=60, 1423 USE_ETAGS=True, 1424 ) 1425 def test_middleware_with_streaming_response(self): 1426 # cache with non empty request.GET 1427 request = self._get_request_cache(query_string='foo=baz&other=true') 1428 1429 # first access, cache must return None 1430 get_cache_data = FetchFromCacheMiddleware().process_request(request) 1431 self.assertEqual(get_cache_data, None) 1432 1433 # pass streaming response through UpdateCacheMiddleware. 1434 content = 'Check for cache with QUERY_STRING and streaming content' 1435 response = StreamingHttpResponse(content) 1436 UpdateCacheMiddleware().process_response(request, response) 1437 1438 # second access, cache must still return None, because we can't cache 1439 # streaming response. 1440 get_cache_data = FetchFromCacheMiddleware().process_request(request) 1441 self.assertEqual(get_cache_data, None) 1442 1419 1443 1420 1444 @override_settings( 1421 1445 CACHES={ -
new file tests/regressiontests/httpwrappers/abc.txt
diff --git a/tests/regressiontests/httpwrappers/abc.txt b/tests/regressiontests/httpwrappers/abc.txt new file mode 100644 index 0000000..6bac42b
- + 1 random content -
tests/regressiontests/httpwrappers/tests.py
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 4c6aed1..5aca8ff 100644
a b 2 2 from __future__ import unicode_literals 3 3 4 4 import copy 5 import os 5 6 import pickle 7 import tempfile 6 8 7 9 from django.core.exceptions import SuspiciousOperation 8 10 from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, 9 11 HttpResponsePermanentRedirect, HttpResponseNotAllowed, 10 HttpResponseNotModified, 12 HttpResponseNotModified, StreamingHttpResponse, 11 13 SimpleCookie, BadHeaderError, 12 14 parse_cookie) 13 15 from django.test import TestCase … … class HttpResponseTests(unittest.TestCase): 351 353 self.assertRaises(SuspiciousOperation, 352 354 HttpResponsePermanentRedirect, url) 353 355 354 355 356 class HttpResponseSubclassesTests(TestCase): 356 357 def test_redirect(self): 357 358 response = HttpResponseRedirect('/redirected/') … … class HttpResponseSubclassesTests(TestCase): 379 380 content_type='text/html') 380 381 self.assertContains(response, 'Only the GET method is allowed', status_code=405) 381 382 383 class StreamingHttpResponseTests(TestCase): 384 def test_streaming_response(self): 385 r = StreamingHttpResponse(iter(['hello', 'world'])) 386 387 # iterating over the response itself yields bytestring chunks. 388 chunks = list(r) 389 self.assertEqual(chunks, [b'hello', b'world']) 390 for chunk in chunks: 391 self.assertIsInstance(chunk, six.binary_type) 392 393 # and the response can only be iterated once. 394 self.assertEqual(list(r), []) 395 396 # even when a sequence that can be iterated many times, like a list, 397 # is given as content. 398 r = StreamingHttpResponse(['abc', 'def']) 399 self.assertEqual(list(r), [b'abc', b'def']) 400 self.assertEqual(list(r), []) 401 402 # streaming responses don't have a `content` attribute. 403 self.assertFalse(hasattr(r, 'content')) 404 405 # and you can't accidentally assign to a `content` attribute. 406 with self.assertRaises(AttributeError): 407 r.content = 'xyz' 408 409 # but they do have a `streaming_content` attribute. 410 self.assertTrue(hasattr(r, 'streaming_content')) 411 412 # that exists so we can check if a response is streaming, and wrap or 413 # replace the content iterator. 414 r.streaming_content = iter(['abc', 'def']) 415 r.streaming_content = (chunk.upper() for chunk in r.streaming_content) 416 self.assertEqual(list(r), [b'ABC', b'DEF']) 417 418 # coercing a streaming response to bytes doesn't return a complete HTTP 419 # message like a regular response does. it only gives us the headers. 420 r = StreamingHttpResponse(iter(['hello', 'world'])) 421 self.assertEqual( 422 six.binary_type(r), b'Content-Type: text/html; charset=utf-8') 423 424 # and this won't consume its content. 425 self.assertEqual(list(r), [b'hello', b'world']) 426 427 # additional content cannot be written to the response. 428 r = StreamingHttpResponse(iter(['hello', 'world'])) 429 with self.assertRaises(Exception): 430 r.write('!') 431 432 # and we can't tell the current position. 433 with self.assertRaises(Exception): 434 r.tell() 435 436 class FileCloseTests(TestCase): 437 def test_response(self): 438 filename = os.path.join(os.path.dirname(__file__), 'abc.txt') 439 440 # file isn't closed until we close the response. 441 file1 = open(filename) 442 r = HttpResponse(file1) 443 self.assertFalse(file1.closed) 444 r.close() 445 self.assertTrue(file1.closed) 446 447 # don't automatically close file when we finish iterating the response. 448 file1 = open(filename) 449 r = HttpResponse(file1) 450 self.assertFalse(file1.closed) 451 list(r) 452 self.assertFalse(file1.closed) 453 r.close() 454 self.assertTrue(file1.closed) 455 456 # when multiple file are assigned as content, make sure they are all 457 # closed with the response. 458 file1 = open(filename) 459 file2 = open(filename) 460 r = HttpResponse(file1) 461 r.content = file2 462 self.assertFalse(file1.closed) 463 self.assertFalse(file2.closed) 464 r.close() 465 self.assertTrue(file1.closed) 466 self.assertTrue(file2.closed) 467 468 def test_streaming_response(self): 469 filename = os.path.join(os.path.dirname(__file__), 'abc.txt') 470 471 # file isn't closed until we close the response. 472 file1 = open(filename) 473 r = StreamingHttpResponse(file1) 474 self.assertFalse(file1.closed) 475 r.close() 476 self.assertTrue(file1.closed) 477 478 # automatically close file when we finish iterating the response. 479 file1 = open(filename) 480 r = StreamingHttpResponse(file1) 481 self.assertFalse(file1.closed) 482 list(r) 483 self.assertTrue(file1.closed) 484 485 # when multiple file are assigned as content, make sure they are all 486 # closed with the response. 487 file1 = open(filename) 488 file2 = open(filename) 489 r = StreamingHttpResponse(file1) 490 r.streaming_content = file2 491 self.assertFalse(file1.closed) 492 self.assertFalse(file2.closed) 493 r.close() 494 self.assertTrue(file1.closed) 495 self.assertTrue(file2.closed) 496 382 497 class CookieTests(unittest.TestCase): 383 498 def test_encode(self): 384 499 """ -
tests/regressiontests/middleware/tests.py
diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py index eb66f2b..de901f4 100644
a b from io import BytesIO 8 8 from django.conf import settings 9 9 from django.core import mail 10 10 from django.http import HttpRequest 11 from django.http import HttpResponse 11 from django.http import HttpResponse, StreamingHttpResponse 12 12 from django.middleware.clickjacking import XFrameOptionsMiddleware 13 13 from django.middleware.common import CommonMiddleware 14 14 from django.middleware.http import ConditionalGetMiddleware … … class ConditionalGetMiddlewareTest(TestCase): 322 322 self.assertTrue('Content-Length' in self.resp) 323 323 self.assertEqual(int(self.resp['Content-Length']), content_length) 324 324 325 def test_content_length_header_not_added(self): 326 resp = StreamingHttpResponse('content') 327 self.assertFalse('Content-Length' in resp) 328 resp = ConditionalGetMiddleware().process_response(self.req, resp) 329 self.assertFalse('Content-Length' in resp) 330 325 331 def test_content_length_header_not_changed(self): 326 332 bad_content_length = len(self.resp.content) + 10 327 333 self.resp['Content-Length'] = bad_content_length … … class ConditionalGetMiddlewareTest(TestCase): 351 357 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 352 358 self.assertEqual(self.resp.status_code, 200) 353 359 360 @override_settings(USE_ETAGS=True) 361 def test_etag(self): 362 req = HttpRequest() 363 res = HttpResponse('content') 364 self.assertTrue( 365 CommonMiddleware().process_response(req, res).has_header('ETag')) 366 367 @override_settings(USE_ETAGS=True) 368 def test_etag_streaming_response(self): 369 req = HttpRequest() 370 res = StreamingHttpResponse(['content']) 371 res['ETag'] = 'tomatoes' 372 self.assertEqual( 373 CommonMiddleware().process_response(req, res).get('ETag'), 374 'tomatoes') 375 376 @override_settings(USE_ETAGS=True) 377 def test_no_etag_streaming_response(self): 378 req = HttpRequest() 379 res = StreamingHttpResponse(['content']) 380 self.assertFalse( 381 CommonMiddleware().process_response(req, res).has_header('ETag')) 382 354 383 # Tests for the Last-Modified header 355 384 356 385 def test_if_modified_since_and_no_last_modified(self): … … class GZipMiddlewareTest(TestCase): 511 540 short_string = b"This string is too short to be worth compressing." 512 541 compressible_string = b'a' * 500 513 542 uncompressible_string = b''.join(six.int2byte(random.randint(0, 255)) for _ in xrange(500)) 543 sequence = [b'a' * 500, b'b' * 200, b'a' * 300] 514 544 515 545 def setUp(self): 516 546 self.req = HttpRequest() … … class GZipMiddlewareTest(TestCase): 525 555 self.resp.status_code = 200 526 556 self.resp.content = self.compressible_string 527 557 self.resp['Content-Type'] = 'text/html; charset=UTF-8' 558 self.stream_resp = StreamingHttpResponse(self.sequence) 559 self.stream_resp['Content-Type'] = 'text/html; charset=UTF-8' 528 560 529 561 @staticmethod 530 562 def decompress(gzipped_string): … … class GZipMiddlewareTest(TestCase): 539 571 self.assertEqual(r.get('Content-Encoding'), 'gzip') 540 572 self.assertEqual(r.get('Content-Length'), str(len(r.content))) 541 573 574 def test_compress_streaming_response(self): 575 """ 576 Tests that compression is performed on responses with streaming content. 577 """ 578 r = GZipMiddleware().process_response(self.req, self.stream_resp) 579 self.assertEqual(self.decompress(b''.join(r)), b''.join(self.sequence)) 580 self.assertEqual(r.get('Content-Encoding'), 'gzip') 581 self.assertFalse(r.has_header('Content-Length')) 582 542 583 def test_compress_non_200_response(self): 543 584 """ 544 585 Tests that compression is performed on responses with a status other than 200.