# HG changeset patch # Parent 8160786d029509a7563a1321068fc330ec2ff0c6 Issue #24291: Avoid WSGIRequestHandler doing partial writes If the underlying send() method indicates a partial write, such as when the call is interrupted to handle a signal, the server would silently drop the remaining data. diff -r 8160786d0295 Doc/library/wsgiref.rst --- a/Doc/library/wsgiref.rst Sat Apr 09 11:33:53 2016 +0200 +++ b/Doc/library/wsgiref.rst Tue May 31 10:52:53 2016 +0000 @@ -515,6 +515,9 @@ streams are stored in the :attr:`stdin`, :attr:`stdout`, :attr:`stderr`, and :attr:`environ` attributes. + The :meth:`~io.BufferedIOBase.write` method of *stdout* should write + each chunk in full, like :class:`io.BufferedIOBase`. + .. class:: BaseHandler() diff -r 8160786d0295 Lib/test/test_wsgiref.py --- a/Lib/test/test_wsgiref.py Sat Apr 09 11:33:53 2016 +0200 +++ b/Lib/test/test_wsgiref.py Tue May 31 10:52:53 2016 +0000 @@ -1,3 +1,5 @@ +from test import support +from test.test_httpservers import NoLogRequestHandler from unittest import TestCase from wsgiref.util import setup_testing_defaults from wsgiref.headers import Headers @@ -6,12 +8,14 @@ from wsgiref.validate import validator from wsgiref.simple_server import WSGIServer, WSGIRequestHandler from wsgiref.simple_server import make_server +from http.client import HTTPConnection from io import StringIO, BytesIO, BufferedReader from socketserver import BaseServer from platform import python_implementation import os import re +import signal import sys import unittest @@ -221,6 +225,56 @@ b"data", out) + def test_interrupted_write(self): + # BaseHandler._write() and _flush() have to write all data, even if + # it takes multiple send() calls. Test this by interrupting a send() + # call with a Unix signal. + threading = support.import_module("threading") + pthread_kill = support.get_attribute(signal, "pthread_kill") + + def app(environ, start_response): + start_response("200 OK", []) + return [bytes(support.SOCK_MAX_SIZE)] + + class WsgiHandler(NoLogRequestHandler, WSGIRequestHandler): + pass + + server = make_server(support.HOST, 0, app, handler_class=WsgiHandler) + 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) + received = None + main_thread = threading.get_ident() + + def run_client(): + http = HTTPConnection(*server.server_address) + http.request("GET", "/") + with http.getresponse() as response: + response.read(100) + # The main thread should now be blocking in a send() system + # call. But in theory, it could be 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 received + received = len(response.read()) + http.close() + + background = threading.Thread(target=run_client) + background.start() + server.handle_request() + background.join() + self.assertEqual(received, support.SOCK_MAX_SIZE - 100) + class UtilityTests(TestCase): diff -r 8160786d0295 Lib/wsgiref/simple_server.py --- a/Lib/wsgiref/simple_server.py Sat Apr 09 11:33:53 2016 +0200 +++ b/Lib/wsgiref/simple_server.py Tue May 31 10:52:53 2016 +0000 @@ -11,6 +11,7 @@ """ from http.server import BaseHTTPRequestHandler, HTTPServer +from io import BufferedWriter import sys import urllib.parse from wsgiref.handlers import SimpleHandler @@ -126,11 +127,17 @@ if not self.parse_request(): # An error code has been sent, just exit return - handler = ServerHandler( - self.rfile, self.wfile, self.get_stderr(), self.get_environ() - ) - handler.request_handler = self # backpointer for logging - handler.run(self.server.get_app()) + # Avoid passing the raw file object wfile, which can do partial + # writes (Issue 24291) + stdout = BufferedWriter(self.wfile) + try: + handler = ServerHandler( + 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() diff -r 8160786d0295 Misc/NEWS --- a/Misc/NEWS Sat Apr 09 11:33:53 2016 +0200 +++ b/Misc/NEWS Tue May 31 10:52:53 2016 +0000 @@ -237,6 +237,10 @@ Library ------- +- Issue #24291: Fix wsgiref.simple_server.WSGIRequestHandler to completely + write data to the client. Previously it could do partial writes and + truncate data. + - Issue #16329: Add .webm to mimetypes.types_map. Patch by Giampaolo Rodola'. - Issue #13952: Add .csv to mimetypes.types_map. Patch by Geoff Wilson.