mirror of https://github.com/python/cpython
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:
parent
87cf220972
commit
c7ae19b6a7
|
@ -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
|
||||||
|
|
|
@ -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('+', ' ')
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue