source: mod_gnutls/test/mgstest/http.py @ f6d2721

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

HTTPSubprocessConnection: Run filter in a thread instead of process

This is a minor performance improvement in this use case (I
measured). Filter and HTTP processing can't really run in parallel,
because the latter needs the output of the former.

  • Property mode set to 100644
File size: 3.9 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
21
22from http.client import HTTPConnection
23from threading import Thread
24
25class HTTPSubprocessConnection(HTTPConnection):
26    """An HTTPConnection that transports data through a subprocess instead
27    of a socket. The mod_gnutls test suite uses it to transport data
28    through gnutls-cli instead of the ssl module.
29
30    """
31    def __init__(self, command, host, port=None,
32                 output_filter=None,
33                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
34                 blocksize=8192):
35        super(HTTPSubprocessConnection, self).__init__(host, port, timeout,
36                                                       source_address=None,
37                                                       blocksize=blocksize)
38        # "command" must be a list containing binary and command line
39        # parameters
40        self.command = command
41        # This will be the subprocess reference when connected
42        self._sproc = None
43        # The subprocess return code is stored here on close()
44        self.returncode = None
45        # The set_tunnel method of the super class is not supported
46        # (see exception doc)
47        self.set_tunnel = None
48        # This method will be run in a separate process and filter the
49        # stdout of self._sproc. Its arguments are self._sproc.stdout
50        # and the socket back to the HTTP connection (write-only).
51        self._output_filter = output_filter
52        # output filter thread
53        self._fthread = None
54
55    def connect(self):
56        s_local, s_remote = socket.socketpair(socket.AF_UNIX,
57                                              socket.SOCK_STREAM)
58        s_local.settimeout(self.timeout)
59
60        # TODO: Maybe capture stderr?
61        if self._output_filter:
62            self._sproc = subprocess.Popen(self.command, stdout=subprocess.PIPE,
63                                           stdin=s_remote, close_fds=True,
64                                           bufsize=0)
65            self._fthread = Thread(target=self._output_filter,
66                                   args=(self._sproc.stdout, s_remote))
67            self._fthread.start()
68        else:
69            self._sproc = subprocess.Popen(self.command, stdout=s_remote,
70                                           stdin=s_remote, close_fds=True,
71                                           bufsize=0)
72        self.sock = s_local
73
74    def close(self):
75        # close socket to subprocess for writing
76        if self.sock:
77            self.sock.shutdown(socket.SHUT_WR)
78
79        # Wait for the process to stop, send SIGTERM/SIGKILL if
80        # necessary
81        if self._sproc:
82            try:
83                self.returncode = self._sproc.wait(self.timeout)
84            except subprocess.TimeoutExpired:
85                try:
86                    self._sproc.terminate()
87                    self.returncode = self._sproc.wait(self.timeout)
88                except subprocess.TimeoutExpired:
89                    self._sproc.kill()
90                    self.returncode = self._sproc.wait(self.timeout)
91
92        # filter thread receives HUP on pipe when the subprocess
93        # terminates
94        if self._fthread:
95            self._fthread.join()
96
97        # close the connection in the super class, which also calls
98        # self.sock.close()
99        super().close()
Note: See TracBrowser for help on using the repository browser.