Issue #26721: Change StreamRequestHandler.wfile to BufferedIOBase
This commit is contained in:
parent
7acc3486d6
commit
34eeed4290
|
@ -98,8 +98,8 @@ of which this module provides three different variants:
|
||||||
|
|
||||||
.. attribute:: rfile
|
.. attribute:: rfile
|
||||||
|
|
||||||
Contains an input stream, positioned at the start of the optional input
|
An :class:`io.BufferedIOBase` input stream, ready to read from
|
||||||
data.
|
the start of the optional input data.
|
||||||
|
|
||||||
.. attribute:: wfile
|
.. attribute:: wfile
|
||||||
|
|
||||||
|
@ -107,6 +107,9 @@ of which this module provides three different variants:
|
||||||
client. Proper adherence to the HTTP protocol must be used when writing to
|
client. Proper adherence to the HTTP protocol must be used when writing to
|
||||||
this stream.
|
this stream.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
This is an :class:`io.BufferedIOBase` stream.
|
||||||
|
|
||||||
:class:`BaseHTTPRequestHandler` has the following attributes:
|
:class:`BaseHTTPRequestHandler` has the following attributes:
|
||||||
|
|
||||||
.. attribute:: server_version
|
.. attribute:: server_version
|
||||||
|
|
|
@ -409,6 +409,15 @@ Request Handler Objects
|
||||||
read or written, respectively, to get the request data or return data
|
read or written, respectively, to get the request data or return data
|
||||||
to the client.
|
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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -373,6 +373,12 @@ defined in :mod:`http.server`, :mod:`xmlrpc.server` and
|
||||||
protocol.
|
protocol.
|
||||||
(Contributed by Aviv Palivoda in :issue:`26404`.)
|
(Contributed by Aviv Palivoda in :issue:`26404`.)
|
||||||
|
|
||||||
|
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`.)
|
||||||
|
|
||||||
|
|
||||||
subprocess
|
subprocess
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -132,6 +132,7 @@ try:
|
||||||
import threading
|
import threading
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import dummy_threading as threading
|
import dummy_threading as threading
|
||||||
|
from io import BufferedIOBase
|
||||||
from time import monotonic as time
|
from time import monotonic as time
|
||||||
|
|
||||||
__all__ = ["BaseServer", "TCPServer", "UDPServer",
|
__all__ = ["BaseServer", "TCPServer", "UDPServer",
|
||||||
|
@ -743,7 +744,10 @@ class StreamRequestHandler(BaseRequestHandler):
|
||||||
self.connection.setsockopt(socket.IPPROTO_TCP,
|
self.connection.setsockopt(socket.IPPROTO_TCP,
|
||||||
socket.TCP_NODELAY, True)
|
socket.TCP_NODELAY, True)
|
||||||
self.rfile = self.connection.makefile('rb', self.rbufsize)
|
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):
|
def finish(self):
|
||||||
if not self.wfile.closed:
|
if not self.wfile.closed:
|
||||||
|
@ -756,6 +760,24 @@ class StreamRequestHandler(BaseRequestHandler):
|
||||||
self.wfile.close()
|
self.wfile.close()
|
||||||
self.rfile.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)
|
||||||
|
with memoryview(b) as view:
|
||||||
|
return view.nbytes
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
return self._sock.fileno()
|
||||||
|
|
||||||
class DatagramRequestHandler(BaseRequestHandler):
|
class DatagramRequestHandler(BaseRequestHandler):
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ Test suite for socketserver.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
|
@ -376,6 +377,84 @@ if HAVE_FORKING:
|
||||||
self.active_children.clear()
|
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):
|
||||||
|
# Test that wfile.write() sends data immediately, and that it does
|
||||||
|
# not truncate sends when interrupted by a Unix signal
|
||||||
|
pthread_kill = test.support.get_attribute(signal, 'pthread_kill')
|
||||||
|
|
||||||
|
class Handler(socketserver.StreamRequestHandler):
|
||||||
|
def handle(self):
|
||||||
|
self.server.sent1 = self.wfile.write(b'write data\n')
|
||||||
|
# Should be sent immediately, without requiring flush()
|
||||||
|
self.server.received = self.rfile.readline()
|
||||||
|
big_chunk = bytes(test.support.SOCK_MAX_SIZE)
|
||||||
|
self.server.sent2 = self.wfile.write(big_chunk)
|
||||||
|
|
||||||
|
server = socketserver.TCPServer((HOST, 0), Handler)
|
||||||
|
self.addCleanup(server.server_close)
|
||||||
|
interrupted = threading.Event()
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
interrupted.set()
|
||||||
|
|
||||||
|
original = signal.signal(signal.SIGUSR1, signal_handler)
|
||||||
|
self.addCleanup(signal.signal, signal.SIGUSR1, original)
|
||||||
|
response1 = None
|
||||||
|
received2 = None
|
||||||
|
main_thread = threading.get_ident()
|
||||||
|
|
||||||
|
def run_client():
|
||||||
|
s = socket.socket(server.address_family, socket.SOCK_STREAM,
|
||||||
|
socket.IPPROTO_TCP)
|
||||||
|
with s, s.makefile('rb') as reader:
|
||||||
|
s.connect(server.server_address)
|
||||||
|
nonlocal response1
|
||||||
|
response1 = reader.readline()
|
||||||
|
s.sendall(b'client response\n')
|
||||||
|
|
||||||
|
reader.read(100)
|
||||||
|
# The main thread should now be blocking in a send() syscall.
|
||||||
|
# But in theory, it could get interrupted by other signals,
|
||||||
|
# and then retried. So keep sending the signal in a loop, in
|
||||||
|
# case an earlier signal happens to be delivered at an
|
||||||
|
# inconvenient moment.
|
||||||
|
while True:
|
||||||
|
pthread_kill(main_thread, signal.SIGUSR1)
|
||||||
|
if interrupted.wait(timeout=float(1)):
|
||||||
|
break
|
||||||
|
nonlocal received2
|
||||||
|
received2 = len(reader.read())
|
||||||
|
|
||||||
|
background = threading.Thread(target=run_client)
|
||||||
|
background.start()
|
||||||
|
server.handle_request()
|
||||||
|
background.join()
|
||||||
|
self.assertEqual(server.sent1, len(response1))
|
||||||
|
self.assertEqual(response1, b'write data\n')
|
||||||
|
self.assertEqual(server.received, b'client response\n')
|
||||||
|
self.assertEqual(server.sent2, test.support.SOCK_MAX_SIZE)
|
||||||
|
self.assertEqual(received2, test.support.SOCK_MAX_SIZE - 100)
|
||||||
|
|
||||||
|
|
||||||
class MiscTestCase(unittest.TestCase):
|
class MiscTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
|
|
|
@ -11,7 +11,6 @@ module. See also the BaseHTTPServer module docs for other API information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from io import BufferedWriter
|
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from wsgiref.handlers import SimpleHandler
|
from wsgiref.handlers import SimpleHandler
|
||||||
|
@ -127,17 +126,11 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
|
||||||
if not self.parse_request(): # An error code has been sent, just exit
|
if not self.parse_request(): # An error code has been sent, just exit
|
||||||
return
|
return
|
||||||
|
|
||||||
# Avoid passing the raw file object wfile, which can do partial
|
handler = ServerHandler(
|
||||||
# writes (Issue 24291)
|
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
|
||||||
stdout = BufferedWriter(self.wfile)
|
)
|
||||||
try:
|
handler.request_handler = self # backpointer for logging
|
||||||
handler = ServerHandler(
|
handler.run(self.server.get_app())
|
||||||
self.rfile, stdout, self.get_stderr(), self.get_environ()
|
|
||||||
)
|
|
||||||
handler.request_handler = self # backpointer for logging
|
|
||||||
handler.run(self.server.get_app())
|
|
||||||
finally:
|
|
||||||
stdout.detach()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ What's New in Python 3.6.0 alpha 3
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #26721: Change the socketserver.StreamRequestHandler.wfile attribute
|
||||||
|
to implement BufferedIOBase. In particular, the write() method no longer
|
||||||
|
does partial writes.
|
||||||
|
|
||||||
- Issue #22115: Added methods trace_add, trace_remove and trace_info in the
|
- Issue #22115: Added methods trace_add, trace_remove and trace_info in the
|
||||||
tkinter.Variable class. They replace old methods trace_variable, trace,
|
tkinter.Variable class. They replace old methods trace_variable, trace,
|
||||||
trace_vdelete and trace_vinfo that use obsolete Tcl commands and might
|
trace_vdelete and trace_vinfo that use obsolete Tcl commands and might
|
||||||
|
|
Loading…
Reference in New Issue