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
This commit is contained in:
Senthil Kumaran 2011-05-09 23:25:02 +08:00
parent 87cf220972
commit c7ae19b6a7
4 changed files with 93 additions and 31 deletions

View File

@ -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:
.. 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

View File

@ -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):
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):
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):
"""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):
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('+', ' ')

View File

@ -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):
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):
pos = f.tell()
f.seek(0)
data = f.read()
f.seek(pos)
return data
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'
handler.requestline = ''
handler.command = None
self.handler.send_header('Foo', 'foo') handler.send_error(418)
self.handler.send_header('bar', 'bar') self.assertEqual(output.numWrites, 2)
self.assertEqual(_readAndReseek(output), b'')
self.handler.end_headers() def test_header_buffering_of_send_response_only(self):
self.assertEqual(_readAndReseek(output),
b'Foo: foo\r\nbar: bar\r\n\r\n') 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_response_only(418)
self.assertEqual(output.numWrites, 0)
handler.end_headers()
self.assertEqual(output.numWrites, 1)
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):

View File

@ -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.