source: mod_gnutls/test/mgstest/http.py @ 4459cdd

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

HTTPSubprocessConnection: Handle subprocess stderr via a thread

This is a workaround to prevent output from gnutls-cli and the Python
interpreter overwriting each other in the test logs. Forcing
gnutls-cli stderr through Python ensures synchronization (via global
interpreter lock).

  • Property mode set to 100644
File size: 4.8 KB
Line 
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
17"""HTTP handling components for mod_gnutls tests."""
18
19import socket
20import subprocess
21import sys
22
23from http.client import HTTPConnection
24from threading import Thread
25
26class HTTPSubprocessConnection(HTTPConnection):
27    """An HTTPConnection that transports data through a subprocess instead
28    of a socket. The mod_gnutls test suite uses it to transport data
29    through gnutls-cli instead of the ssl module.
30
31    """
32    def __init__(self, command, host, port=None,
33                 output_filter=None,
34                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
35                 blocksize=8192):
36        super(HTTPSubprocessConnection, self).__init__(host, port, timeout,
37                                                       source_address=None,
38                                                       blocksize=blocksize)
39        # "command" must be a list containing binary and command line
40        # parameters
41        self.command = command
42        # This will be the subprocess reference when connected
43        self._sproc = None
44        # The subprocess return code is stored here on close()
45        self.returncode = None
46        # The set_tunnel method of the super class is not supported
47        # (see exception doc)
48        self.set_tunnel = None
49        # This method will be run in a separate process and filter the
50        # stdout of self._sproc. Its arguments are self._sproc.stdout
51        # and the socket back to the HTTP connection (write-only).
52        self._output_filter = output_filter
53        # output filter thread
54        self._fthread = None
55        # Error stream handler thread. This is needed to synchronize
56        # output between Python and the subprocess.
57        self._ethread = None
58
59    def connect(self):
60        s_local, s_remote = socket.socketpair(socket.AF_UNIX,
61                                              socket.SOCK_STREAM)
62        s_local.settimeout(self.timeout)
63
64        # TODO: Maybe capture stderr?
65        if self._output_filter:
66            self._sproc = subprocess.Popen(self.command, stdout=subprocess.PIPE,
67                                           stderr=subprocess.PIPE,
68                                           stdin=s_remote, close_fds=True,
69                                           bufsize=0)
70            self._fthread = Thread(target=self._output_filter,
71                                   args=(self._sproc.stdout, s_remote))
72            self._fthread.start()
73        else:
74            self._sproc = subprocess.Popen(self.command, stdout=s_remote,
75                                           stderr=subprocess.PIPE,
76                                           stdin=s_remote, close_fds=True,
77                                           bufsize=0)
78        self._ethread = Thread(target=_stderr_writer,
79                               args=(self._sproc.stderr,))
80        self._ethread.start()
81        self.sock = s_local
82
83    def close(self):
84        # close socket to subprocess for writing
85        if self.sock:
86            self.sock.shutdown(socket.SHUT_WR)
87
88        # Wait for the process to stop, send SIGTERM/SIGKILL if
89        # necessary
90        if self._sproc:
91            try:
92                self.returncode = self._sproc.wait(self.timeout)
93            except subprocess.TimeoutExpired:
94                try:
95                    self._sproc.terminate()
96                    self.returncode = self._sproc.wait(self.timeout)
97                except subprocess.TimeoutExpired:
98                    self._sproc.kill()
99                    self.returncode = self._sproc.wait(self.timeout)
100
101        # filter thread receives HUP on pipe when the subprocess
102        # terminates
103        if self._fthread:
104            self._fthread.join()
105        if self._ethread:
106            self._ethread.join()
107
108        # close the connection in the super class, which also calls
109        # self.sock.close()
110        super().close()
111
112
113
114def _stderr_writer(stream):
115    """Flush incoming data to sys.stderr.
116
117    This is a workaround to prevent output from gnutls-cli and the
118    Python interpreter overwriting each other in the test
119    logs. Forcing gnutls-cli stderr through Python ensures
120    synchronization (via global interpreter lock).
121    """
122    for line in stream:
123        print(line.decode(), file=sys.stderr, end='', flush=True)
Note: See TracBrowser for help on using the repository browser.