source: mod_gnutls/test/mgstest/http.py

asyncio
Last change on this file was 60a415a, checked in by Fiona Klute <fiona.klute@…>, 3 months ago

Fix simple formatting issues reported by flake8

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