source: mod_gnutls/test/mgstest/http.py @ 442c6a6

asyncioproxy-ticket
Last change on this file since 442c6a6 was b307cc1, checked in by Fiona Klute <fiona.klute@…>, 16 months ago

mgstest.http._stderr_writer: Always close input stream at EOS

Fixes "ResourceWarning?: unclosed file" after using an
HTTPSubprocessConnection.

  • Property mode set to 100644
File size: 5.1 KB
RevLine 
[6d3dc34]1#!/usr/bin/python3
2
3# Copyright 2019 Fiona Klute
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
[0560bb9]17"""HTTP handling components for mod_gnutls tests."""
18
[b307cc1]19import contextlib
[6d3dc34]20import socket
21import subprocess
[4459cdd]22import sys
[6d3dc34]23
24from http.client import HTTPConnection
[f6d2721]25from threading import Thread
[6d3dc34]26
27class HTTPSubprocessConnection(HTTPConnection):
[0560bb9]28    """An HTTPConnection that transports data through a subprocess instead
29    of a socket. The mod_gnutls test suite uses it to transport data
30    through gnutls-cli instead of the ssl module.
31
32    """
[6d3dc34]33    def __init__(self, command, host, port=None,
34                 output_filter=None,
[3be92d3]35                 stderr_log=None,
[006f91a]36                 timeout=socket.getdefaulttimeout(),
[6d3dc34]37                 blocksize=8192):
38        super(HTTPSubprocessConnection, self).__init__(host, port, timeout,
39                                                       source_address=None,
40                                                       blocksize=blocksize)
41        # "command" must be a list containing binary and command line
42        # parameters
43        self.command = command
44        # This will be the subprocess reference when connected
45        self._sproc = None
46        # The subprocess return code is stored here on close()
47        self.returncode = None
48        # The set_tunnel method of the super class is not supported
49        # (see exception doc)
50        self.set_tunnel = None
51        # This method will be run in a separate process and filter the
52        # stdout of self._sproc. Its arguments are self._sproc.stdout
53        # and the socket back to the HTTP connection (write-only).
54        self._output_filter = output_filter
[3be92d3]55        # If not None, write a copy of the subprocess' stderr to here.
56        self._stderr_log = stderr_log
[f6d2721]57        # output filter thread
58        self._fthread = None
[4459cdd]59        # Error stream handler thread. This is needed to synchronize
60        # output between Python and the subprocess.
61        self._ethread = None
[6d3dc34]62
63    def connect(self):
64        s_local, s_remote = socket.socketpair(socket.AF_UNIX,
65                                              socket.SOCK_STREAM)
66        s_local.settimeout(self.timeout)
67
68        if self._output_filter:
69            self._sproc = subprocess.Popen(self.command, stdout=subprocess.PIPE,
[4459cdd]70                                           stderr=subprocess.PIPE,
[6d3dc34]71                                           stdin=s_remote, close_fds=True,
72                                           bufsize=0)
[f6d2721]73            self._fthread = Thread(target=self._output_filter,
74                                   args=(self._sproc.stdout, s_remote))
75            self._fthread.start()
[6d3dc34]76        else:
77            self._sproc = subprocess.Popen(self.command, stdout=s_remote,
[4459cdd]78                                           stderr=subprocess.PIPE,
[6d3dc34]79                                           stdin=s_remote, close_fds=True,
80                                           bufsize=0)
[4459cdd]81        self._ethread = Thread(target=_stderr_writer,
[3be92d3]82                               args=(self._sproc.stderr, self._stderr_log))
[4459cdd]83        self._ethread.start()
[6d3dc34]84        self.sock = s_local
85
86    def close(self):
87        # close socket to subprocess for writing
88        if self.sock:
89            self.sock.shutdown(socket.SHUT_WR)
90
91        # Wait for the process to stop, send SIGTERM/SIGKILL if
92        # necessary
93        if self._sproc:
94            try:
95                self.returncode = self._sproc.wait(self.timeout)
96            except subprocess.TimeoutExpired:
97                try:
98                    self._sproc.terminate()
99                    self.returncode = self._sproc.wait(self.timeout)
100                except subprocess.TimeoutExpired:
101                    self._sproc.kill()
102                    self.returncode = self._sproc.wait(self.timeout)
103
[f6d2721]104        # filter thread receives HUP on pipe when the subprocess
[6d3dc34]105        # terminates
[f6d2721]106        if self._fthread:
107            self._fthread.join()
[4459cdd]108        if self._ethread:
109            self._ethread.join()
[6d3dc34]110
111        # close the connection in the super class, which also calls
112        # self.sock.close()
113        super().close()
[4459cdd]114
115
116
[3be92d3]117def _stderr_writer(stream, copy=None):
118    """Flush incoming data to sys.stderr, and optionally to "copy".
[4459cdd]119
120    This is a workaround to prevent output from gnutls-cli and the
121    Python interpreter overwriting each other in the test
122    logs. Forcing gnutls-cli stderr through Python ensures
123    synchronization (via global interpreter lock).
[3be92d3]124
[b307cc1]125    The incoming stream is closed after all lines have been read.
126
[4459cdd]127    """
[b307cc1]128    with contextlib.closing(stream):
129        for line in stream:
130            print(line.decode(), file=sys.stderr, end='', flush=True)
131            if copy:
132                print(line.decode(), file=copy, end='')
Note: See TracBrowser for help on using the repository browser.