Skip to content

Commit 1b57f6d

Browse files
committed
Deprecated Makefile in favor of StreamReader and StreamWriter
We replace the `MakeFile` factory function with direct class instantiation. The `MakeFile` function now emits a `DeprecationWarning` and will be removed in a future release. `makefile()` methods are also deprecated on the adapters and its base class.
1 parent 4b4bb7f commit 1b57f6d

File tree

11 files changed

+300
-70
lines changed

11 files changed

+300
-70
lines changed

cheroot/connections.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from . import errors
1313
from ._compat import IS_WINDOWS
14-
from .makefile import MakeFile
1514

1615

1716
try:
@@ -304,7 +303,6 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
304303
if hasattr(s, 'settimeout'):
305304
s.settimeout(self.server.timeout)
306305

307-
mf = MakeFile
308306
ssl_env = {}
309307
# if ssl cert and key are set, we try to be a secure HTTP server
310308
if self.server.ssl_adapter is not None:
@@ -327,12 +325,12 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
327325
)
328326
self._send_bad_request_plain_http_error(s)
329327
return None
330-
mf = self.server.ssl_adapter.makefile
328+
331329
# Re-apply our timeout since we may have a new socket object
332330
if hasattr(s, 'settimeout'):
333331
s.settimeout(self.server.timeout)
334332

335-
conn = self.server.ConnectionClass(self.server, s, mf)
333+
conn = self.server.ConnectionClass(self.server, s)
336334

337335
if not isinstance(self.server.bind_addr, (str, bytes)):
338336
# optional values

cheroot/makefile.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# prefer slower Python-based io module
44
import _pyio as io
55
import socket
6+
from warnings import warn as _warn
67

78

89
# Write only 16K at a time to sockets
@@ -70,6 +71,16 @@ def write(self, val, *args, **kwargs):
7071

7172

7273
def MakeFile(sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE):
73-
"""File object attached to a socket object."""
74+
"""
75+
File object attached to a socket object.
76+
77+
This function is now deprecated: Use
78+
StreamReader or StreamWriter directly.
79+
"""
80+
_warn(
81+
'MakeFile is deprecated. Use StreamReader or StreamWriter directly.',
82+
DeprecationWarning,
83+
stacklevel=2,
84+
)
7485
cls = StreamReader if 'r' in mode else StreamWriter
7586
return cls(sock, mode, bufsize)

cheroot/server.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484

8585
from . import __version__, connections, errors
8686
from ._compat import IS_PPC, bton
87-
from .makefile import MakeFile, StreamWriter
87+
from .makefile import StreamReader, StreamWriter
8888
from .workers import threadpool
8989

9090

@@ -1276,19 +1276,31 @@ class HTTPConnection:
12761276
# Fields set by ConnectionManager.
12771277
last_used = None
12781278

1279-
def __init__(self, server, sock, makefile=MakeFile):
1279+
def __init__(self, server, sock, makefile=None):
12801280
"""Initialize HTTPConnection instance.
12811281
12821282
Args:
12831283
server (HTTPServer): web server object receiving this request
12841284
sock (socket._socketobject): the raw socket object (usually
12851285
TCP) for this connection
1286-
makefile (file): a fileobject class for reading from the socket
1286+
makefile (file): Now deprecated
1287+
Use to be a fileobject class for reading from the socket
12871288
"""
12881289
self.server = server
12891290
self.socket = sock
1290-
self.rfile = makefile(sock, 'rb', self.rbufsize)
1291-
self.wfile = makefile(sock, 'wb', self.wbufsize)
1291+
1292+
if makefile is not None:
1293+
_warn(
1294+
'The `makefile` parameter in creating an `HTTPConnection` '
1295+
'is deprecated and will be removed in a future version. '
1296+
'The connection socket should now be fully wrapped by the '
1297+
'adapter before being passed to this constructor.',
1298+
DeprecationWarning,
1299+
stacklevel=2,
1300+
)
1301+
1302+
self.rfile = StreamReader(sock, 'rb', self.rbufsize)
1303+
self.wfile = StreamWriter(sock, 'wb', self.wbufsize)
12921304
self.requests_seen = 0
12931305

12941306
self.peercreds_enabled = self.server.peercreds_enabled
@@ -1358,12 +1370,8 @@ def communicate(self): # noqa: C901 # FIXME
13581370
def _handle_no_ssl(self, req):
13591371
if not req or req.sent_headers:
13601372
return
1361-
# Unwrap wfile
1362-
try:
1363-
resp_sock = self.socket._sock
1364-
except AttributeError:
1365-
# self.socket is of OpenSSL.SSL.Connection type
1366-
resp_sock = self.socket._socket
1373+
# Unwrap to get raw TCP socket
1374+
resp_sock = self.socket._sock
13671375
self.wfile = StreamWriter(resp_sock, 'wb', self.wbufsize)
13681376
msg = (
13691377
'The client sent a plain HTTP request, but '

cheroot/ssl/__init__.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@ def _ensure_peer_speaks_https(raw_socket, /) -> None:
5555

5656

5757
class Adapter(ABC):
58-
"""Base class for SSL driver library adapters.
58+
"""
59+
Base class for SSL driver library adapters.
5960
6061
Required methods:
62+
``wrap(sock)``: Wrap a socket with SSL. Also returns ssl environ dict.
63+
``get_environ()``: Get SSL environment variables as a dict.
6164
62-
* ``wrap(sock) -> (wrapped socket, ssl environ dict)``
63-
* ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) ->
64-
socket file object``
65+
Optional methods:
66+
``makefile(sock, mode, bufsize)``: Create custom file object.
67+
The ``pyOpenSSL`` adapter uses this to handle SSL-specific errors.
6568
"""
6669

6770
@abstractmethod
@@ -111,5 +114,9 @@ def get_environ(self):
111114

112115
@abstractmethod
113116
def makefile(self, sock, mode='r', bufsize=-1):
114-
"""Return socket file object."""
117+
"""
118+
Return socket file object.
119+
120+
This method is now deprecated. It will be removed in a future version.
121+
"""
115122
raise NotImplementedError # pragma: no cover

cheroot/ssl/builtin.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,15 @@
1111
import sys
1212
import threading
1313
from contextlib import suppress
14+
from warnings import warn as _warn
1415

1516

1617
try:
1718
import ssl
1819
except ImportError:
1920
ssl = None
2021

21-
try:
22-
from _pyio import DEFAULT_BUFFER_SIZE
23-
except ImportError:
24-
try:
25-
from io import DEFAULT_BUFFER_SIZE
26-
except ImportError:
27-
DEFAULT_BUFFER_SIZE = -1
28-
2922
from .. import errors
30-
from ..makefile import StreamReader, StreamWriter
3123
from ..server import HTTPServer
3224
from . import Adapter
3325

@@ -493,7 +485,19 @@ def _make_env_dn_dict(self, env_prefix, cert_value):
493485
env['%s_%s_%i' % (env_prefix, attr_code, i)] = val
494486
return env
495487

496-
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
497-
"""Return socket file object."""
498-
cls = StreamReader if 'r' in mode else StreamWriter
499-
return cls(sock, mode, bufsize)
488+
def makefile(self, sock, mode='r', bufsize=-1):
489+
"""
490+
Return socket file object.
491+
492+
``makefile`` is now deprecated and will be removed in a future
493+
version.
494+
"""
495+
_warn(
496+
'The `makefile` method is deprecated and will be removed in a future version. '
497+
'The connection socket should be fully wrapped by the adapter '
498+
'before being passed to the HTTPConnection constructor.',
499+
DeprecationWarning,
500+
stacklevel=2,
501+
)
502+
503+
return sock.makefile(mode, bufsize)

cheroot/ssl/builtin.pyi

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import typing as _t
22

33
from . import Adapter
44

5-
DEFAULT_BUFFER_SIZE: int
6-
75
class BuiltinSSLAdapter(Adapter):
86
CERT_KEY_TO_ENV: _t.Any
97
CERT_KEY_TO_LDAP_CODE: _t.Any

cheroot/ssl/pyopenssl.py

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
pyopenssl
5151
"""
5252

53+
import io
5354
import socket
5455
import sys
5556
import threading
@@ -74,7 +75,6 @@
7475
errors,
7576
server as cheroot_server,
7677
)
77-
from ..makefile import StreamReader, StreamWriter
7878
from . import Adapter
7979

8080

@@ -168,14 +168,6 @@ def send(self, *args, **kwargs):
168168
)
169169

170170

171-
class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader):
172-
"""SSL file object attached to a socket object."""
173-
174-
175-
class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter):
176-
"""SSL file object attached to a socket object."""
177-
178-
179171
class SSLConnectionProxyMeta:
180172
"""Metaclass for generating a bunch of proxy methods."""
181173

@@ -345,9 +337,11 @@ def wrap(self, sock):
345337
)
346338

347339
conn = SSLConnection(self.context, sock)
348-
349340
conn.set_accept_state() # Tell OpenSSL this is a server connection
350-
return conn, self._environ.copy()
341+
342+
# Wrap the SSLConnection to provide standard socket interface
343+
tls_socket = TLSSocket(conn)
344+
return tls_socket, self._environ.copy()
351345

352346
def _password_callback(
353347
self,
@@ -450,17 +444,101 @@ def get_environ(self):
450444
return ssl_environ
451445

452446
def makefile(self, sock, mode='r', bufsize=-1):
453-
"""Return socket file object."""
454-
cls = (
455-
SSLFileobjectStreamReader
456-
if 'r' in mode
457-
else SSLFileobjectStreamWriter
447+
"""
448+
Return socket file object.
449+
450+
``makefile`` is now deprecated and will be removed in a future
451+
version.
452+
"""
453+
_warn(
454+
'The `makefile` method is deprecated and will be removed in a future version. '
455+
'The connection socket should be fully wrapped by the adapter '
456+
'before being passed to the HTTPConnection constructor.',
457+
DeprecationWarning,
458+
stacklevel=2,
458459
)
459-
# sock is an pyopenSSL.SSLConnection instance here
460-
if SSL and isinstance(sock, SSLConnection):
461-
wrapped_socket = cls(sock, mode, bufsize)
462-
wrapped_socket.ssl_timeout = sock.gettimeout()
463-
return wrapped_socket
464-
# This is from past:
465-
# TODO: figure out what it's meant for
466-
return cheroot_server.CP_fileobject(sock, mode, bufsize)
460+
461+
return sock.makefile(mode, bufsize)
462+
463+
464+
class RawIOWrapperMixin:
465+
"""
466+
Provide methods to satisfy the io.RawIOBase interface.
467+
468+
Delegates I/O to the core socket methods recv_into and send,
469+
which are implemented by TLSSocket.
470+
"""
471+
472+
def readable(self):
473+
"""Return whether object supports reading."""
474+
return True
475+
476+
def writable(self):
477+
"""Return whether object supports writing."""
478+
return True
479+
480+
def readinto(self, b):
481+
"""Read bytes into a pre-allocated buffer (I/O interface)."""
482+
# This relies on the concrete class implementing recv_into
483+
return self.recv_into(b)
484+
485+
def write(self, b):
486+
"""Write bytes (I/O interface)."""
487+
# This relies on the concrete class implementing send
488+
return self.send(b)
489+
490+
491+
class TLSSocket(RawIOWrapperMixin, SSLFileobjectMixin, io.RawIOBase):
492+
"""
493+
Wrap pyOpenSSL SSL.Connection to work with StreamReader/StreamWriter.
494+
495+
Handles OpenSSL-specific errors internally so that standard Python I/O
496+
classes can use it transparently.
497+
"""
498+
499+
def __init__(self, ssl_conn, ssl_timeout=None, ssl_retry=0.01):
500+
"""Initialize with an SSL.Connection object."""
501+
self._ssl_conn = ssl_conn
502+
self._sock = ssl_conn._socket # Store reference to raw TCP socket
503+
self._lock = threading.RLock()
504+
self.ssl_timeout = ssl_timeout or 3.0
505+
self.ssl_retry = ssl_retry
506+
507+
# Socket I/O
508+
# _safe_call is delegated to the SSLFileobjectMixin
509+
510+
def recv_into(self, buffer, nbytes=None):
511+
"""Receive data into a buffer (socket interface)."""
512+
with self._lock:
513+
return self._safe_call(
514+
True, # is_reader=True
515+
self._ssl_conn.recv_into,
516+
buffer,
517+
nbytes,
518+
)
519+
520+
def send(self, data):
521+
"""Send data (socket interface)."""
522+
with self._lock:
523+
return self._safe_call(
524+
False, # is_reader=False
525+
self._ssl_conn.send,
526+
data,
527+
)
528+
529+
def fileno(self):
530+
"""Return the file descriptor."""
531+
return self._ssl_conn.fileno()
532+
533+
def _decref_socketios(self):
534+
"""Shutdown the connection."""
535+
return self._ssl_conn._decref_socketios()
536+
537+
def shutdown(self, how):
538+
"""Shutdown the connection."""
539+
return self._ssl_conn.shutdown(how)
540+
541+
def close(self):
542+
"""Close the connection."""
543+
super().close()
544+
self._ssl_conn.close()

0 commit comments

Comments
 (0)