Skip to content

Commit b5cd936

Browse files
committed
Refactor replacing SSLConnectionProxyMeta
Removes custom thread-safety locking logic as the wrapper is not required to be thread-safe. The wrapper now inherits simply from SSL.Connection.
1 parent 22bb430 commit b5cd936

File tree

6 files changed

+52
-134
lines changed

6 files changed

+52
-134
lines changed

cheroot/server.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,12 +1537,7 @@ def peer_group(self):
15371537

15381538
def _close_kernel_socket(self):
15391539
"""Terminate the connection at the transport level."""
1540-
# Honor ``sock_shutdown`` for PyOpenSSL connections.
1541-
shutdown = getattr(
1542-
self.socket,
1543-
'sock_shutdown',
1544-
self.socket.shutdown,
1545-
)
1540+
shutdown = self.socket.sock_shutdown
15461541

15471542
try:
15481543
shutdown(socket.SHUT_RDWR) # actually send a TCP FIN

cheroot/ssl/pyopenssl.py

Lines changed: 38 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252

5353
import socket
5454
import sys
55-
import threading
5655
import time
5756
from warnings import warn as _warn
5857

@@ -176,99 +175,51 @@ class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter):
176175
"""SSL file object attached to a socket object."""
177176

178177

179-
class SSLConnectionProxyMeta:
180-
"""Metaclass for generating a bunch of proxy methods."""
181-
182-
def __new__(mcl, name, bases, nmspc):
183-
"""Attach a list of proxy methods to a new class."""
184-
proxy_methods = (
185-
'get_context',
186-
'pending',
187-
'send',
188-
'write',
189-
'recv',
190-
'read',
191-
'renegotiate',
192-
'bind',
193-
'listen',
194-
'connect',
195-
'accept',
196-
'setblocking',
197-
'fileno',
198-
'close',
199-
'get_cipher_list',
200-
'getpeername',
201-
'getsockname',
202-
'getsockopt',
203-
'setsockopt',
204-
'makefile',
205-
'get_app_data',
206-
'set_app_data',
207-
'state_string',
208-
'sock_shutdown',
209-
'get_peer_certificate',
210-
'want_read',
211-
'want_write',
212-
'set_connect_state',
213-
'set_accept_state',
214-
'connect_ex',
215-
'sendall',
216-
'settimeout',
217-
'gettimeout',
218-
'shutdown',
219-
)
220-
proxy_methods_no_args = ('shutdown',)
221-
222-
proxy_props = ('family',)
223-
224-
def lock_decorator(method):
225-
"""Create a proxy method for a new class."""
226-
227-
def proxy_wrapper(self, *args):
228-
self._lock.acquire()
229-
try:
230-
new_args = (
231-
args[:] if method not in proxy_methods_no_args else []
232-
)
233-
return getattr(self._ssl_conn, method)(*new_args)
234-
finally:
235-
self._lock.release()
236-
237-
return proxy_wrapper
238-
239-
for m in proxy_methods:
240-
nmspc[m] = lock_decorator(m)
241-
nmspc[m].__name__ = m
242-
243-
def make_property(property_):
244-
"""Create a proxy method for a new class."""
245-
246-
def proxy_prop_wrapper(self):
247-
return getattr(self._ssl_conn, property_)
178+
class SSLConnection(SSL.Connection):
179+
"""
180+
A compatibility wrapper around :py:class:`OpenSSL.SSL.Connection`.
248181
249-
proxy_prop_wrapper.__name__ = property_
250-
return property(proxy_prop_wrapper)
182+
This class exists primarily to ensure the standard Python socket method
183+
:py:meth:`.shutdown()` is available for interface compatibility.
184+
"""
251185

252-
for p in proxy_props:
253-
nmspc[p] = make_property(p)
186+
def makefile(self, *args, **kwargs):
187+
"""
188+
Raise :exc:`NotImplementedError` for ``makefile()`` interface.
254189
255-
# Doesn't work via super() for some reason.
256-
# Falling back to type() instead:
257-
return type(name, bases, nmspc)
190+
PyOpenSSL's :py:class:`~OpenSSL.SSL.Connection` marks
191+
:py:meth:`~OpenSSL.SSL.Connection.makefile` as not implemented.
192+
This method exists only for interface compatibility with
193+
Python sockets.
194+
"""
195+
# Replicate the parent's behavior to avoid calling a method
196+
# that is known to be unimplemented.
197+
raise NotImplementedError(
198+
"PyOpenSSL's Connection class does not support the makefile() interface",
199+
)
258200

201+
def shutdown(self, how=None):
202+
"""Shutdown the SSL connection.
259203
260-
class SSLConnection(metaclass=SSLConnectionProxyMeta):
261-
r"""A thread-safe wrapper for an ``SSL.Connection``.
204+
:param how: Ignored. PyOpenSSL's
205+
:py:meth:`~OpenSSL.SSL.Connection.shutdown`
206+
method does not accept any arguments.
207+
Present here for interface compatibility with Python
208+
:py:meth:`~socket.socket.shutdown` that
209+
:py:class:`ssl.SSLSocket` wrapper exposes.
210+
:type how: object
211+
"""
212+
return super().shutdown()
262213

263-
:param tuple args: the arguments to create the wrapped \
264-
:py:class:`SSL.Connection(*args) \
265-
<pyopenssl:OpenSSL.SSL.Connection>`
266-
"""
214+
def sock_shutdown(self, how=None):
215+
"""Shutdown the SSL connection.
267216
268-
def __init__(self, *args):
269-
"""Initialize SSLConnection instance."""
270-
self._ssl_conn = SSL.Connection(*args)
271-
self._lock = threading.RLock()
217+
This method is provided for interface compatibility and delegates
218+
directly to the standard shutdown() method.
219+
"""
220+
# We call self.shutdown(how) to use the method where we added
221+
# the compatibility logic (handling 'how=None' for the parent).
222+
return self.shutdown(how)
272223

273224

274225
class pyOpenSSLAdapter(Adapter):

cheroot/ssl/pyopenssl.pyi

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ class SSLFileobjectMixin:
1818
class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): ... # type:ignore[misc]
1919
class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): ... # type:ignore[misc]
2020

21-
class SSLConnectionProxyMeta:
22-
def __new__(mcl, name, bases, nmspc): ...
21+
class SSLConnection(SSL.Connection):
22+
proxy_methods: tuple[str, ...]
23+
proxy_methods_no_args: tuple[str, ...]
24+
proxy_props: tuple[str, ...]
2325

24-
class SSLConnection:
25-
def __init__(self, *args) -> None: ...
26+
def shutdown(self, how: int | None = None) -> None: ... # type: ignore[override]
27+
def sock_shutdown(self, how: int | None = None) -> None: ... # type: ignore[override]
2628

2729
class pyOpenSSLAdapter(Adapter):
2830
def __init__(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Removed thread-safety locking logic from
2+
:py:class:`cheroot.ssl.pyopenssl.SSLConnection` as
3+
this wrapper did not need to be thread-safe.
4+
5+
-- by :user:`julianz-`

docs/spelling_wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ positionally
4444
pre
4545
preconfigure
4646
py
47+
pyOpenSSL
4748
pytest
4849
pythonic
4950
readonly
@@ -75,6 +76,7 @@ tuples
7576
unbuffered
7677
unclosed
7778
unfilterable
79+
unhandled
7880
unregister
7981
unregisters
8082
uptime

stubtest_allowlist.txt

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,3 @@
1-
# generated members by metaclass
2-
cheroot.ssl.pyopenssl.SSLConnection.accept
3-
cheroot.ssl.pyopenssl.SSLConnection.bind
4-
cheroot.ssl.pyopenssl.SSLConnection.close
5-
cheroot.ssl.pyopenssl.SSLConnection.connect
6-
cheroot.ssl.pyopenssl.SSLConnection.connect_ex
7-
cheroot.ssl.pyopenssl.SSLConnection.family
8-
cheroot.ssl.pyopenssl.SSLConnection.fileno
9-
cheroot.ssl.pyopenssl.SSLConnection.get_app_data
10-
cheroot.ssl.pyopenssl.SSLConnection.get_cipher_list
11-
cheroot.ssl.pyopenssl.SSLConnection.get_context
12-
cheroot.ssl.pyopenssl.SSLConnection.get_peer_certificate
13-
cheroot.ssl.pyopenssl.SSLConnection.getpeername
14-
cheroot.ssl.pyopenssl.SSLConnection.getsockname
15-
cheroot.ssl.pyopenssl.SSLConnection.getsockopt
16-
cheroot.ssl.pyopenssl.SSLConnection.gettimeout
17-
cheroot.ssl.pyopenssl.SSLConnection.listen
18-
cheroot.ssl.pyopenssl.SSLConnection.makefile
19-
cheroot.ssl.pyopenssl.SSLConnection.pending
20-
cheroot.ssl.pyopenssl.SSLConnection.read
21-
cheroot.ssl.pyopenssl.SSLConnection.recv
22-
cheroot.ssl.pyopenssl.SSLConnection.renegotiate
23-
cheroot.ssl.pyopenssl.SSLConnection.send
24-
cheroot.ssl.pyopenssl.SSLConnection.sendall
25-
cheroot.ssl.pyopenssl.SSLConnection.set_accept_state
26-
cheroot.ssl.pyopenssl.SSLConnection.set_app_data
27-
cheroot.ssl.pyopenssl.SSLConnection.set_connect_state
28-
cheroot.ssl.pyopenssl.SSLConnection.setblocking
29-
cheroot.ssl.pyopenssl.SSLConnection.setsockopt
30-
cheroot.ssl.pyopenssl.SSLConnection.settimeout
31-
cheroot.ssl.pyopenssl.SSLConnection.shutdown
32-
cheroot.ssl.pyopenssl.SSLConnection.sock_shutdown
33-
cheroot.ssl.pyopenssl.SSLConnection.state_string
34-
cheroot.ssl.pyopenssl.SSLConnection.want_read
35-
cheroot.ssl.pyopenssl.SSLConnection.want_write
36-
cheroot.ssl.pyopenssl.SSLConnection.write
37-
381
# suppress is both a function and class
392
cheroot._compat.suppress
403

0 commit comments

Comments
 (0)