Ticket #32472: wip_patch.diff

File wip_patch.diff, 8.2 KB (added by David Sanders, 3 years ago)

WIP Patch With Chunked Encoding

  • django/core/servers/basehttp.py

    diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
    index 02957c51a2..d0a74f2c7b 100644
    a b This is a simple server for use in testing or debugging Django apps. It hasn't  
    77been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
    88"""
    99
     10from http import HTTPStatus
    1011import logging
    1112import socket
    1213import socketserver
    class ServerHandler(simple_server.ServerHandler):  
    9495            content_length = 0
    9596        super().__init__(LimitedStream(stdin, content_length), stdout, stderr, environ, **kwargs)
    9697
     98    def _write(self, data):
     99        if self.headers_sent and self.headers.get('Transfer-Encoding', False) == "chunked":
     100            data = format(len(data), "X").encode() + b"\r\n" + data + b"\r\n"
     101
     102        super()._write(data)
     103
     104    def send_headers(self):
     105        """Transmit headers to the client, via self._write()"""
     106        self.cleanup_headers()
     107        if not self.origin_server or self.client_is_modern():
     108            self.send_preamble()
     109            self._write(bytes(self.headers))
     110        # Only mark headers as sent after they're actually written
     111        self.headers_sent = True
     112
     113    def finish_content(self):
     114        super().finish_content()
     115
     116        if self.headers_sent and self.headers.get('Transfer-Encoding', False) == "chunked":
     117            self._write(b'')  # End message for chunked encoding
     118
     119    def set_content_length(self):
     120        """Compute Content-Length or switch to chunked encoding if possible"""
     121        try:
     122            blocks = len(self.result)
     123        except (TypeError, AttributeError, NotImplementedError):
     124            pass
     125        else:
     126            if blocks == 1:
     127                self.headers['Content-Length'] = str(self.bytes_sent)
     128                return
     129
     130        # Switch to chunked encoding
     131        self.headers['Transfer-Encoding'] = 'chunked'
     132
    97133    def cleanup_headers(self):
    98134        super().cleanup_headers()
    99         # HTTP/1.1 requires support for persistent connections. Send 'close' if
    100         # the content length is unknown to prevent clients from reusing the
    101         # connection.
    102         if 'Content-Length' not in self.headers:
    103             self.headers['Connection'] = 'close'
    104135        # Persistent connections require threading server.
    105         elif not isinstance(self.request_handler.server, socketserver.ThreadingMixIn):
     136        if not isinstance(self.request_handler.server, socketserver.ThreadingMixIn):
    106137            self.headers['Connection'] = 'close'
    107138        # Mark the connection for closing if it's set as such above or if the
    108139        # application sent the header.
    class WSGIRequestHandler(simple_server.WSGIRequestHandler):  
    172203        self.handle_one_request()
    173204        while not self.close_connection:
    174205            self.handle_one_request()
     206
     207        # Wait for the connection to be closed by the client to ensure
     208        # that all data was received. Shutting down the connection seems
     209        # to flush any data in the send buffer and immediately ends the
     210        # connection, which risks having large responses cut off.
     211        if getattr(self, 'request_version', None) == 'HTTP/1.1':
     212            self.rfile.peek()
     213
    175214        try:
    176215            self.connection.shutdown(socket.SHUT_WR)
    177216        except (AttributeError, OSError):
    178217            pass
    179218
    180219    def handle_one_request(self):
    181         """Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
     220        """
     221        Copy of WSGIRequestHandler.handle() but with different ServerHandler,
     222        and re-aligned with BaseHTTPRequestHandler.handle()
     223        """
    182224        self.raw_requestline = self.rfile.readline(65537)
    183225        if len(self.raw_requestline) > 65536:
    184226            self.requestline = ''
    185227            self.request_version = ''
    186228            self.command = ''
    187             self.send_error(414)
     229            self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
     230            return
     231
     232        if not self.raw_requestline:
     233            self.close_connection = True
    188234            return
    189235
    190236        if not self.parse_request():  # An error code has been sent, just exit
    class WSGIRequestHandler(simple_server.WSGIRequestHandler):  
    196242        handler.request_handler = self      # backpointer for logging & connection closing
    197243        handler.run(self.server.get_app())
    198244
     245        self.wfile.flush() #actually send the response if not already done.
     246
    199247
    200248def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    201249    server_address = (addr, port)
  • tests/servers/test_basehttp.py

    diff --git a/tests/servers/test_basehttp.py b/tests/servers/test_basehttp.py
    index 32fdbf3c0e..4bedc390dc 100644
    a b  
    1 from io import BytesIO
     1from io import BytesIO, BufferedReader
    22
    33from django.core.handlers.wsgi import WSGIRequest
    44from django.core.servers.basehttp import WSGIRequestHandler
    class WSGIRequestHandlerTestCase(SimpleTestCase):  
    1919
    2020    def test_log_message(self):
    2121        request = WSGIRequest(self.request_factory.get('/').environ)
    22         request.makefile = lambda *args, **kwargs: BytesIO()
     22        request.makefile = lambda *args, **kwargs: BufferedReader(BytesIO())
    2323        handler = WSGIRequestHandler(request, '192.168.0.2', None)
    2424        level_status_codes = {
    2525            'info': [200, 301, 304],
    class WSGIRequestHandlerTestCase(SimpleTestCase):  
    4141
    4242    def test_https(self):
    4343        request = WSGIRequest(self.request_factory.get('/').environ)
    44         request.makefile = lambda *args, **kwargs: BytesIO()
     44        request.makefile = lambda *args, **kwargs: BufferedReader(BytesIO())
    4545
    4646        handler = WSGIRequestHandler(request, '192.168.0.2', None)
    4747
    class WSGIRequestHandlerTestCase(SimpleTestCase):  
    7575        rfile.write(b"Some_Header: bad\r\n")
    7676        rfile.write(b"Other_Header: bad\r\n")
    7777        rfile.seek(0)
     78        rfile = BufferedReader(rfile)
    7879
    7980        # WSGIRequestHandler closes the output file; we need to make this a
    8081        # no-op so we can still read its contents.
  • tests/servers/tests.py

    diff --git a/tests/servers/tests.py b/tests/servers/tests.py
    index 33d0605443..d239d34a49 100644
    a b class LiveServerViews(LiveServerBase):  
    6969
    7070    def test_closes_connection_without_content_length(self):
    7171        """
    72         A HTTP 1.1 server is supposed to support keep-alive. Since our
    73         development server is rather simple we support it only in cases where
    74         we can detect a content length from the response. This should be doable
    75         for all simple views and streaming responses where an iterable with
    76         length of one is passed. The latter follows as result of `set_content_length`
    77         from https://github.com/python/cpython/blob/master/Lib/wsgiref/handlers.py.
    78 
    79         If we cannot detect a content length we explicitly set the `Connection`
    80         header to `close` to notify the client that we do not actually support
    81         it.
     72        TODO - Updated description
    8273        """
    8374        conn = HTTPConnection(LiveServerViews.server_thread.host, LiveServerViews.server_thread.port, timeout=1)
    8475        try:
    8576            conn.request('GET', '/streaming_example_view/', headers={'Connection': 'keep-alive'})
    8677            response = conn.getresponse()
    87             self.assertTrue(response.will_close)
     78            self.assertFalse(response.will_close)
    8879            self.assertEqual(response.read(), b'Iamastream')
    8980            self.assertEqual(response.status, 200)
    90             self.assertEqual(response.getheader('Connection'), 'close')
     81            self.assertIsNone(response.getheader('Connection'))
    9182
    9283            conn.request('GET', '/streaming_example_view/', headers={'Connection': 'close'})
    9384            response = conn.getresponse()
    94             self.assertTrue(response.will_close)
     85            self.assertFalse(response.will_close)
    9586            self.assertEqual(response.read(), b'Iamastream')
    9687            self.assertEqual(response.status, 200)
    97             self.assertEqual(response.getheader('Connection'), 'close')
     88            self.assertIsNone(response.getheader('Connection'))
    9889        finally:
    9990            conn.close()
    10091
Back to Top