1. import sys
    
  2. import traceback
    
  3. from io import BytesIO
    
  4. from unittest import TestCase, mock
    
  5. from wsgiref import simple_server
    
  6. 
    
  7. from django.core.servers.basehttp import get_internal_wsgi_application
    
  8. from django.core.signals import request_finished
    
  9. from django.test import RequestFactory, override_settings
    
  10. 
    
  11. from .views import FILE_RESPONSE_HOLDER
    
  12. 
    
  13. # If data is too large, socket will choke, so write chunks no larger than 32MB
    
  14. # at a time. The rationale behind the 32MB can be found in #5596#comment:4.
    
  15. MAX_SOCKET_CHUNK_SIZE = 32 * 1024 * 1024  # 32 MB
    
  16. 
    
  17. 
    
  18. class ServerHandler(simple_server.ServerHandler):
    
  19.     error_status = "500 INTERNAL SERVER ERROR"
    
  20. 
    
  21.     def write(self, data):
    
  22.         """'write()' callable as specified by PEP 3333"""
    
  23. 
    
  24.         assert isinstance(data, bytes), "write() argument must be bytestring"
    
  25. 
    
  26.         if not self.status:
    
  27.             raise AssertionError("write() before start_response()")
    
  28. 
    
  29.         elif not self.headers_sent:
    
  30.             # Before the first output, send the stored headers
    
  31.             self.bytes_sent = len(data)  # make sure we know content-length
    
  32.             self.send_headers()
    
  33.         else:
    
  34.             self.bytes_sent += len(data)
    
  35. 
    
  36.         # XXX check Content-Length and truncate if too many bytes written?
    
  37.         data = BytesIO(data)
    
  38.         for chunk in iter(lambda: data.read(MAX_SOCKET_CHUNK_SIZE), b""):
    
  39.             self._write(chunk)
    
  40.             self._flush()
    
  41. 
    
  42.     def error_output(self, environ, start_response):
    
  43.         super().error_output(environ, start_response)
    
  44.         return ["\n".join(traceback.format_exception(*sys.exc_info()))]
    
  45. 
    
  46. 
    
  47. class DummyHandler:
    
  48.     def log_request(self, *args, **kwargs):
    
  49.         pass
    
  50. 
    
  51. 
    
  52. class FileWrapperHandler(ServerHandler):
    
  53.     def __init__(self, *args, **kwargs):
    
  54.         super().__init__(*args, **kwargs)
    
  55.         self.request_handler = DummyHandler()
    
  56.         self._used_sendfile = False
    
  57. 
    
  58.     def sendfile(self):
    
  59.         self._used_sendfile = True
    
  60.         return True
    
  61. 
    
  62. 
    
  63. def wsgi_app(environ, start_response):
    
  64.     start_response("200 OK", [("Content-Type", "text/plain")])
    
  65.     return [b"Hello World!"]
    
  66. 
    
  67. 
    
  68. def wsgi_app_file_wrapper(environ, start_response):
    
  69.     start_response("200 OK", [("Content-Type", "text/plain")])
    
  70.     return environ["wsgi.file_wrapper"](BytesIO(b"foo"))
    
  71. 
    
  72. 
    
  73. class WSGIFileWrapperTests(TestCase):
    
  74.     """
    
  75.     The wsgi.file_wrapper works for the builtin server.
    
  76. 
    
  77.     Tests for #9659: wsgi.file_wrapper in the builtin server.
    
  78.     We need to mock a couple of handlers and keep track of what
    
  79.     gets called when using a couple kinds of WSGI apps.
    
  80.     """
    
  81. 
    
  82.     def test_file_wrapper_uses_sendfile(self):
    
  83.         env = {"SERVER_PROTOCOL": "HTTP/1.0"}
    
  84.         handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env)
    
  85.         handler.run(wsgi_app_file_wrapper)
    
  86.         self.assertTrue(handler._used_sendfile)
    
  87.         self.assertEqual(handler.stdout.getvalue(), b"")
    
  88.         self.assertEqual(handler.stderr.getvalue(), b"")
    
  89. 
    
  90.     def test_file_wrapper_no_sendfile(self):
    
  91.         env = {"SERVER_PROTOCOL": "HTTP/1.0"}
    
  92.         handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env)
    
  93.         handler.run(wsgi_app)
    
  94.         self.assertFalse(handler._used_sendfile)
    
  95.         self.assertEqual(handler.stdout.getvalue().splitlines()[-1], b"Hello World!")
    
  96.         self.assertEqual(handler.stderr.getvalue(), b"")
    
  97. 
    
  98.     @override_settings(ROOT_URLCONF="builtin_server.urls")
    
  99.     def test_file_response_closing(self):
    
  100.         """
    
  101.         View returning a FileResponse properly closes the file and http
    
  102.         response when file_wrapper is used.
    
  103.         """
    
  104.         env = RequestFactory().get("/fileresponse/").environ
    
  105.         handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env)
    
  106.         handler.run(get_internal_wsgi_application())
    
  107.         # Sendfile is used only when file_wrapper has been used.
    
  108.         self.assertTrue(handler._used_sendfile)
    
  109.         # Fetch the original response object.
    
  110.         self.assertIn("response", FILE_RESPONSE_HOLDER)
    
  111.         response = FILE_RESPONSE_HOLDER["response"]
    
  112.         # The response and file buffers are closed.
    
  113.         self.assertIs(response.closed, True)
    
  114.         buf1, buf2 = FILE_RESPONSE_HOLDER["buffers"]
    
  115.         self.assertIs(buf1.closed, True)
    
  116.         self.assertIs(buf2.closed, True)
    
  117.         FILE_RESPONSE_HOLDER.clear()
    
  118. 
    
  119.     @override_settings(ROOT_URLCONF="builtin_server.urls")
    
  120.     def test_file_response_call_request_finished(self):
    
  121.         env = RequestFactory().get("/fileresponse/").environ
    
  122.         handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env)
    
  123.         with mock.MagicMock() as signal_handler:
    
  124.             request_finished.connect(signal_handler)
    
  125.             handler.run(get_internal_wsgi_application())
    
  126.             self.assertEqual(signal_handler.call_count, 1)
    
  127. 
    
  128. 
    
  129. class WriteChunkCounterHandler(ServerHandler):
    
  130.     """
    
  131.     Server handler that counts the number of chunks written after headers were
    
  132.     sent. Used to make sure large response body chunking works properly.
    
  133.     """
    
  134. 
    
  135.     def __init__(self, *args, **kwargs):
    
  136.         super().__init__(*args, **kwargs)
    
  137.         self.request_handler = DummyHandler()
    
  138.         self.headers_written = False
    
  139.         self.write_chunk_counter = 0
    
  140. 
    
  141.     def send_headers(self):
    
  142.         super().send_headers()
    
  143.         self.headers_written = True
    
  144. 
    
  145.     def _write(self, data):
    
  146.         if self.headers_written:
    
  147.             self.write_chunk_counter += 1
    
  148.         self.stdout.write(data)
    
  149. 
    
  150. 
    
  151. def send_big_data_app(environ, start_response):
    
  152.     start_response("200 OK", [("Content-Type", "text/plain")])
    
  153.     # Return a blob of data that is 1.5 times the maximum chunk size.
    
  154.     return [b"x" * (MAX_SOCKET_CHUNK_SIZE + MAX_SOCKET_CHUNK_SIZE // 2)]
    
  155. 
    
  156. 
    
  157. class ServerHandlerChunksProperly(TestCase):
    
  158.     """
    
  159.     The ServerHandler chunks data properly.
    
  160. 
    
  161.     Tests for #18972: The logic that performs the math to break data into
    
  162.     32MB (MAX_SOCKET_CHUNK_SIZE) chunks was flawed, BUT it didn't actually
    
  163.     cause any problems.
    
  164.     """
    
  165. 
    
  166.     def test_chunked_data(self):
    
  167.         env = {"SERVER_PROTOCOL": "HTTP/1.0"}
    
  168.         handler = WriteChunkCounterHandler(None, BytesIO(), BytesIO(), env)
    
  169.         handler.run(send_big_data_app)
    
  170.         self.assertEqual(handler.write_chunk_counter, 2)