diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index 2f2801239e5..f68d2207d44 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -182,22 +182,29 @@ of which this module provides three different variants: .. method:: send_header(keyword, value) - Writes a specific HTTP header to the output stream. *keyword* should - specify the header keyword, with *value* specifying its value. + Stores the HTTP header to an internal buffer which will be written to the + output stream when :meth:`end_headers` method is invoked. + *keyword* should specify the header keyword, with *value* + specifying its value. + + .. versionchanged:: 3.2 Storing the headers in an internal buffer + .. method:: send_response_only(code, message=None) Sends the reponse header only, used for the purposes when ``100 - Continue`` response is sent by the server to the client. If the *message* - is not specified, the HTTP message corresponding the response *code* is - sent. + Continue`` response is sent by the server to the client. The headers not + buffered and sent directly the output stream.If the *message* is not + specified, the HTTP message corresponding the response *code* is sent. .. versionadded:: 3.2 .. method:: end_headers() - Sends a blank line, indicating the end of the HTTP headers in the - response. + Write the buffered HTTP headers to the output stream and send a blank + line, indicating the end of the HTTP headers in the response. + + .. versionchanged:: 3.2 Writing the buffered headers to the output stream. .. method:: log_request(code='-', size='-') diff --git a/Lib/http/server.py b/Lib/http/server.py index 8a6ba70a8ab..5ebfd25ecec 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -443,7 +443,10 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): def send_header(self, keyword, value): """Send a MIME header.""" if self.request_version != 'HTTP/0.9': - self.wfile.write(("%s: %s\r\n" % (keyword, value)).encode('ASCII', 'strict')) + if not hasattr(self, '_headers_buffer'): + self._headers_buffer = [] + self._headers_buffer.append( + ("%s: %s\r\n" % (keyword, value)).encode('ASCII', 'strict')) if keyword.lower() == 'connection': if value.lower() == 'close': @@ -454,7 +457,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): def end_headers(self): """Send the blank line ending the MIME headers.""" if self.request_version != 'HTTP/0.9': - self.wfile.write(b"\r\n") + self._headers_buffer.append(b"\r\n") + self.wfile.write(b"".join(self._headers_buffer)) + self._headers_buffer = [] def log_request(self, code='-', size='-'): """Log an accepted request. diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index daf37b2788c..86ce91287bc 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -511,6 +511,49 @@ class BaseHTTPRequestHandlerTestCase(unittest.TestCase): self.verify_get_called() self.assertEqual(result[-1], b'Data\r\n') + def test_header_buffering(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') + output = BytesIO() + self.handler.rfile = input + self.handler.wfile = output + self.handler.request_version = 'HTTP/1.1' + + self.handler.send_header('Foo', 'foo') + self.handler.send_header('bar', 'bar') + self.assertEqual(_readAndReseek(output), b'') + self.handler.end_headers() + self.assertEqual(_readAndReseek(output), + b'Foo: foo\r\nbar: bar\r\n\r\n') + + def test_header_unbuffered_when_continue(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\nExpect: 100-continue\r\n\r\n') + output = BytesIO() + self.handler.rfile = input + self.handler.wfile = output + self.handler.request_version = 'HTTP/1.1' + + self.handler.handle_one_request() + self.assertNotEqual(_readAndReseek(output), b'') + result = _readAndReseek(output).split(b'\r\n') + self.assertEqual(result[0], b'HTTP/1.1 100 Continue') + self.assertEqual(result[1], b'HTTP/1.1 200 OK') + def test_with_continue_rejected(self): usual_handler = self.handler # Save to avoid breaking any subsequent tests. self.handler = RejectingSocketlessRequestHandler() diff --git a/Misc/NEWS b/Misc/NEWS index 8f09b5f5efc..e68a9d7df49 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -32,6 +32,10 @@ Core and Builtins Library ------- +- Issue #3709: BaseHTTPRequestHandler will buffer the headers and write to + output stream only when end_headers is invoked. This is a speedup and an + internal optimization. Patch by endian. + - Issue #10220: Added inspect.getgeneratorstate. Initial patch by Rodolpho Eckhardt.