Commit c7ae19b6 authored by Senthil Kumaran's avatar Senthil Kumaran

Issue #3709: a flush_headers method to BaseHTTPRequestHandler which manages the

sending of headers to output stream and flushing the internal headers buffer.
Patch contribution by Andrew Schaaf
parent 87cf2209
...@@ -179,16 +179,17 @@ of which this module provides three different variants: ...@@ -179,16 +179,17 @@ of which this module provides three different variants:
.. method:: send_response(code, message=None) .. method:: send_response(code, message=None)
Sends a response header and logs the accepted request. The HTTP response Adds a response header to the headers buffer and logs the accepted
line is sent, followed by *Server* and *Date* headers. The values for request. The HTTP response line is sent, followed by *Server* and
these two headers are picked up from the :meth:`version_string` and *Date* headers. The values for these two headers are picked up from
:meth:`date_time_string` methods, respectively. the :meth:`version_string` and :meth:`date_time_string` methods,
respectively.
.. method:: send_header(keyword, value) .. method:: send_header(keyword, value)
Stores the HTTP header to an internal buffer which will be written to the Adds the HTTP header to an internal buffer which will be written to the
output stream when :meth:`end_headers` method is invoked. output stream when either :meth:`end_headers` or :meth:`flush_headers`
*keyword* should specify the header keyword, with *value* is invoked. *keyword* should specify the header keyword, with *value*
specifying its value. specifying its value.
.. versionchanged:: 3.2 Storing the headers in an internal buffer .. versionchanged:: 3.2 Storing the headers in an internal buffer
...@@ -205,11 +206,19 @@ of which this module provides three different variants: ...@@ -205,11 +206,19 @@ of which this module provides three different variants:
.. method:: end_headers() .. method:: end_headers()
Write the buffered HTTP headers to the output stream and send a blank Adds a blank line
line, indicating the end of the HTTP headers in the response. (indicating the end of the HTTP headers in the response)
to the headers buffer and calls :meth:`flush_headers()`
.. versionchanged:: 3.2 Writing the buffered headers to the output stream. .. versionchanged:: 3.2 Writing the buffered headers to the output stream.
.. method:: flush_headers()
Finally send the headers to the output stream and flush the internal
headers buffer.
.. versionadded:: 3.3
.. method:: log_request(code='-', size='-') .. method:: log_request(code='-', size='-')
Logs an accepted (successful) request. *code* should specify the numeric Logs an accepted (successful) request. *code* should specify the numeric
......
...@@ -355,6 +355,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): ...@@ -355,6 +355,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
""" """
self.send_response_only(100) self.send_response_only(100)
self.flush_headers()
return True return True
def handle_one_request(self): def handle_one_request(self):
...@@ -432,7 +433,8 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): ...@@ -432,7 +433,8 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
self.wfile.write(content.encode('UTF-8', 'replace')) self.wfile.write(content.encode('UTF-8', 'replace'))
def send_response(self, code, message=None): def send_response(self, code, message=None):
"""Send the response header and log the response code. """Add the response header to the headers buffer and log the
response code.
Also send two standard headers with the server software Also send two standard headers with the server software
version and the current date. version and the current date.
...@@ -451,11 +453,14 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): ...@@ -451,11 +453,14 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
else: else:
message = '' message = ''
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
self.wfile.write(("%s %d %s\r\n" % if not hasattr(self, '_headers_buffer'):
(self.protocol_version, code, message)).encode('latin-1', 'strict')) self._headers_buffer = []
self._headers_buffer.append(("%s %d %s\r\n" %
(self.protocol_version, code, message)).encode(
'latin-1', 'strict'))
def send_header(self, keyword, value): def send_header(self, keyword, value):
"""Send a MIME header.""" """Send a MIME header to the headers buffer."""
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
if not hasattr(self, '_headers_buffer'): if not hasattr(self, '_headers_buffer'):
self._headers_buffer = [] self._headers_buffer = []
...@@ -472,6 +477,10 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): ...@@ -472,6 +477,10 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
"""Send the blank line ending the MIME headers.""" """Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9': if self.request_version != 'HTTP/0.9':
self._headers_buffer.append(b"\r\n") self._headers_buffer.append(b"\r\n")
self.flush_headers()
def flush_headers(self):
if hasattr(self, '_headers_buffer'):
self.wfile.write(b"".join(self._headers_buffer)) self.wfile.write(b"".join(self._headers_buffer))
self._headers_buffer = [] self._headers_buffer = []
...@@ -1081,6 +1090,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): ...@@ -1081,6 +1090,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
env.setdefault(k, "") env.setdefault(k, "")
self.send_response(200, "Script output follows") self.send_response(200, "Script output follows")
self.flush_headers()
decoded_query = query.replace('+', ' ') decoded_query = query.replace('+', ' ')
......
...@@ -461,6 +461,23 @@ class RejectingSocketlessRequestHandler(SocketlessRequestHandler): ...@@ -461,6 +461,23 @@ class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
self.send_error(417) self.send_error(417)
return False return False
class AuditableBytesIO:
def __init__(self):
self.datas = []
def write(self, data):
self.datas.append(data)
def getData(self):
return b''.join(self.datas)
@property
def numWrites(self):
return len(self.datas)
class BaseHTTPRequestHandlerTestCase(unittest.TestCase): class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
"""Test the functionality of the BaseHTTPServer. """Test the functionality of the BaseHTTPServer.
...@@ -527,27 +544,49 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase): ...@@ -527,27 +544,49 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
self.verify_get_called() self.verify_get_called()
self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n') self.assertEqual(result[-1], b'<html><body>Data</body></html>\r\n')
def test_header_buffering(self): def test_header_buffering_of_send_error(self):
def _readAndReseek(f): input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
pos = f.tell() output = AuditableBytesIO()
f.seek(0) handler = SocketlessRequestHandler()
data = f.read() handler.rfile = input
f.seek(pos) handler.wfile = output
return data handler.request_version = 'HTTP/1.1'
handler.requestline = ''
handler.command = None
handler.send_error(418)
self.assertEqual(output.numWrites, 2)
def test_header_buffering_of_send_response_only(self):
input = BytesIO(b'GET / HTTP/1.1\r\n\r\n') input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
output = BytesIO() output = AuditableBytesIO()
self.handler.rfile = input handler = SocketlessRequestHandler()
self.handler.wfile = output handler.rfile = input
self.handler.request_version = 'HTTP/1.1' handler.wfile = output
handler.request_version = 'HTTP/1.1'
self.handler.send_header('Foo', 'foo') handler.send_response_only(418)
self.handler.send_header('bar', 'bar') self.assertEqual(output.numWrites, 0)
self.assertEqual(_readAndReseek(output), b'') handler.end_headers()
self.handler.end_headers() self.assertEqual(output.numWrites, 1)
self.assertEqual(_readAndReseek(output),
b'Foo: foo\r\nbar: bar\r\n\r\n') def test_header_buffering_of_send_header(self):
input = BytesIO(b'GET / HTTP/1.1\r\n\r\n')
output = AuditableBytesIO()
handler = SocketlessRequestHandler()
handler.rfile = input
handler.wfile = output
handler.request_version = 'HTTP/1.1'
handler.send_header('Foo', 'foo')
handler.send_header('bar', 'bar')
self.assertEqual(output.numWrites, 0)
handler.end_headers()
self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
self.assertEqual(output.numWrites, 1)
def test_header_unbuffered_when_continue(self): def test_header_unbuffered_when_continue(self):
......
...@@ -140,6 +140,10 @@ Core and Builtins ...@@ -140,6 +140,10 @@ Core and Builtins
Library Library
------- -------
- Issue #3709: a flush_headers method to BaseHTTPRequestHandler which manages
the sending of headers to output stream and flushing the internal headers
buffer. Patch contribution by Andrew Schaaf
- Issue #11743: Rewrite multiprocessing connection classes in pure Python. - Issue #11743: Rewrite multiprocessing connection classes in pure Python.
- Issue #11164: Stop trying to use _xmlplus in the xml module. - Issue #11164: Stop trying to use _xmlplus in the xml module.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment