# HG changeset patch # Parent 8160786d029509a7563a1321068fc330ec2ff0c6 Issue #26721: Change StreamRequestHandler.wfile to BufferedIOBase diff -r 8160786d0295 Doc/library/http.server.rst --- a/Doc/library/http.server.rst Sat Apr 09 11:33:53 2016 +0200 +++ b/Doc/library/http.server.rst Sun Apr 10 02:20:58 2016 +0000 @@ -100,8 +100,8 @@ .. attribute:: rfile - Contains an input stream, positioned at the start of the optional input - data. + An :class:`io.BufferedIOBase` input stream, ready to read from + the start of the optional input data. .. attribute:: wfile @@ -109,6 +109,9 @@ client. Proper adherence to the HTTP protocol must be used when writing to this stream. + .. versionchanged:: 3.6 + This is an :class:`io.BufferedIOBase` stream. + :class:`BaseHTTPRequestHandler` has the following class variables: .. attribute:: server_version diff -r 8160786d0295 Doc/library/socketserver.rst --- a/Doc/library/socketserver.rst Sat Apr 09 11:33:53 2016 +0200 +++ b/Doc/library/socketserver.rst Sun Apr 10 02:20:58 2016 +0000 @@ -401,6 +401,15 @@ read or written, respectively, to get the request data or return data to the client. + The :attr:`rfile` attributes of both classes support the + :class:`io.BufferedIOBase` readable interface, and + :attr:`DatagramRequestHandler.wfile` supports the + :class:`io.BufferedIOBase` writable interface. + + .. versionchanged:: 3.6 + :attr:`StreamRequestHandler.wfile` also supports the + :class:`io.BufferedIOBase` writable interface. + Examples -------- diff -r 8160786d0295 Doc/whatsnew/3.6.rst --- a/Doc/whatsnew/3.6.rst Sat Apr 09 11:33:53 2016 +0200 +++ b/Doc/whatsnew/3.6.rst Sun Apr 10 02:20:58 2016 +0000 @@ -259,6 +259,16 @@ (Contributed by Wolfgang Langner in :issue:`26587`). +socketserver +------------ + +The :attr:`~socketserver.StreamRequestHandler.wfile` attribute of +:class:`~socketserver.StreamRequestHandler` classes now implements +the :class:`io.BufferedIOBase` writable interface. In particular, +calling :meth:`~io.BufferedIOBase.write` is now guaranteed to send the +data in full. (Contributed by Martin Panter in :issue:`26721`.) + + telnetlib --------- diff -r 8160786d0295 Lib/socketserver.py --- a/Lib/socketserver.py Sat Apr 09 11:33:53 2016 +0200 +++ b/Lib/socketserver.py Sun Apr 10 02:20:58 2016 +0000 @@ -132,6 +132,7 @@ import threading except ImportError: import dummy_threading as threading +from io import BytesIO, BufferedIOBase from time import monotonic as time __all__ = ["BaseServer", "TCPServer", "UDPServer", "ForkingUDPServer", @@ -734,7 +735,10 @@ self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) - self.wfile = self.connection.makefile('wb', self.wbufsize) + if self.wbufsize == 0: + self.wfile = _SocketWriter(self.connection) + else: + self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: @@ -747,13 +751,29 @@ self.wfile.close() self.rfile.close() +class _SocketWriter(BufferedIOBase): + """Simple writable BufferedIOBase implementation for a socket + + Does not hold data in a buffer, avoiding any need to call flush().""" + + def __init__(self, sock): + self._sock = sock + + def writable(self): + return True + + def write(self, b): + self._sock.sendall(b) + return len(b) + + def fileno(self): + return self._sock.fileno() class DatagramRequestHandler(BaseRequestHandler): """Define self.rfile and self.wfile for datagram sockets.""" def setup(self): - from io import BytesIO self.packet, self.socket = self.request self.rfile = BytesIO(self.packet) self.wfile = BytesIO() diff -r 8160786d0295 Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py Sat Apr 09 11:33:53 2016 +0200 +++ b/Lib/test/test_socketserver.py Sun Apr 10 02:20:58 2016 +0000 @@ -3,6 +3,7 @@ """ import contextlib +import io import os import select import signal @@ -372,6 +373,48 @@ self.active_children.clear() +class SocketWriterTest(unittest.TestCase): + def test_basics(self): + class Handler(socketserver.StreamRequestHandler): + def handle(self): + self.server.wfile = self.wfile + self.server.wfile_fileno = self.wfile.fileno() + self.server.request_fileno = self.request.fileno() + + server = socketserver.TCPServer((HOST, 0), Handler) + self.addCleanup(server.server_close) + s = socket.socket( + server.address_family, socket.SOCK_STREAM, socket.IPPROTO_TCP) + with s: + s.connect(server.server_address) + server.handle_request() + self.assertIsInstance(server.wfile, io.BufferedIOBase) + self.assertEqual(server.wfile_fileno, server.request_fileno) + + @unittest.skipUnless(threading, 'Threading required for this test.') + def test_write(self): + class Handler(socketserver.StreamRequestHandler): + def handle(self): + self.server.sent = self.wfile.write(b'write data\n') + # Should be sent immediately, without requiring flush() + self.server.received = self.rfile.readline() + + server = socketserver.TCPServer((HOST, 0), Handler) + self.addCleanup(server.server_close) + background = threading.Thread(target=server.handle_request) + background.start() + s = socket.socket( + server.address_family, socket.SOCK_STREAM, socket.IPPROTO_TCP) + with s, s.makefile('rb') as reader: + s.connect(server.server_address) + response = reader.readline() + s.sendall(b'client response\n') + background.join() + self.assertEqual(server.sent, len(response)) + self.assertEqual(response, b'write data\n') + self.assertEqual(server.received, b'client response\n') + + class MiscTestCase(unittest.TestCase): def test_all(self): diff -r 8160786d0295 Misc/NEWS --- a/Misc/NEWS Sat Apr 09 11:33:53 2016 +0200 +++ b/Misc/NEWS Sun Apr 10 02:20:58 2016 +0000 @@ -237,6 +237,10 @@ Library ------- +- Issue #26721: Change the socketserver.StreamRequestHandler.wfile attribute + to implement BufferedIOBase. In particular, the write() method no longer + does partial writes. + - Issue #16329: Add .webm to mimetypes.types_map. Patch by Giampaolo Rodola'. - Issue #13952: Add .csv to mimetypes.types_map. Patch by Geoff Wilson.