Skip to content

Commit eee0bb4

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 c5d5175 commit eee0bb4

File tree

11 files changed

+491
-95
lines changed

11 files changed

+491
-95
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: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,16 @@
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

91+
try:
92+
from OpenSSL import SSL
93+
except ImportError:
94+
SSL = None # OpenSSL not available
95+
96+
9197
__all__ = (
9298
'ChunkedRFile',
9399
'DropUnderscoreHeaderReader',
@@ -1276,19 +1282,31 @@ class HTTPConnection:
12761282
# Fields set by ConnectionManager.
12771283
last_used = None
12781284

1279-
def __init__(self, server, sock, makefile=MakeFile):
1285+
def __init__(self, server, sock, makefile=None):
12801286
"""Initialize HTTPConnection instance.
12811287
12821288
Args:
12831289
server (HTTPServer): web server object receiving this request
12841290
sock (socket._socketobject): the raw socket object (usually
12851291
TCP) for this connection
1286-
makefile (file): a fileobject class for reading from the socket
1292+
makefile (file): Now deprecated.
1293+
Used to be a fileobject class for reading from the socket.
12871294
"""
12881295
self.server = server
12891296
self.socket = sock
1290-
self.rfile = makefile(sock, 'rb', self.rbufsize)
1291-
self.wfile = makefile(sock, 'wb', self.wbufsize)
1297+
1298+
if makefile is not None:
1299+
_warn(
1300+
'The `makefile` parameter in creating an `HTTPConnection` '
1301+
'is deprecated and will be removed in a future version. '
1302+
'The connection socket should now be fully wrapped by the '
1303+
'adapter before being passed to this constructor.',
1304+
DeprecationWarning,
1305+
stacklevel=2,
1306+
)
1307+
1308+
self.rfile = StreamReader(sock, 'rb', self.rbufsize)
1309+
self.wfile = StreamWriter(sock, 'wb', self.wbufsize)
12921310
self.requests_seen = 0
12931311

12941312
self.peercreds_enabled = self.server.peercreds_enabled
@@ -1358,12 +1376,8 @@ def communicate(self): # noqa: C901 # FIXME
13581376
def _handle_no_ssl(self, req):
13591377
if not req or req.sent_headers:
13601378
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
1379+
# Unwrap to get raw TCP socket
1380+
resp_sock = self.socket._sock
13671381
self.wfile = StreamWriter(resp_sock, 'wb', self.wbufsize)
13681382
msg = (
13691383
'The client sent a plain HTTP request, but '
@@ -1507,20 +1521,23 @@ def peer_group(self):
15071521

15081522
def _close_kernel_socket(self):
15091523
"""Terminate the connection at the transport level."""
1510-
# Honor ``sock_shutdown`` for PyOpenSSL connections.
1511-
shutdown = getattr(
1512-
self.socket,
1513-
'sock_shutdown',
1514-
self.socket.shutdown,
1515-
)
1516-
15171524
try:
1518-
shutdown(socket.SHUT_RDWR) # actually send a TCP FIN
1525+
self.socket.shutdown(socket.SHUT_RDWR)
15191526
except errors.acceptable_sock_shutdown_exceptions:
15201527
pass
15211528
except socket.error as e:
15221529
if e.errno not in errors.acceptable_sock_shutdown_error_codes:
15231530
raise
1531+
except Exception as exc:
1532+
# translate SSL exceptions to FatalSSLAlert
1533+
if SSL is not None and isinstance(
1534+
exc,
1535+
(SSL.Error, SSL.SysCallError),
1536+
):
1537+
raise errors.FatalSSLAlert(
1538+
'TLS/SSL connection failure',
1539+
) from exc
1540+
raise # re-raise everything else
15241541

15251542

15261543
class HTTPServer:

cheroot/ssl/__init__.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,21 @@ class Adapter(ABC):
6161
Required methods:
6262
6363
* ``wrap(sock) -> (wrapped socket, ssl environ dict)``
64-
* ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) ->
65-
socket file object``
64+
* ``get_environ() -> (ssl environ dict)``
65+
66+
Optional methods:
67+
68+
* ``makefile(sock, mode='r', bufsize=-1) -> socket file object``
69+
70+
This method is deprecated and will be removed in a future release.
71+
72+
Historically, the ``PyOpenSSL`` adapter used ``makefile()`` to
73+
wrap the underlying socket in an ``OpenSSL``-aware file object
74+
so that Cheroot's HTTP request parser (which expects file-like I/O
75+
such as ``readline()``) could read from TLS connections. The
76+
adapter now fully wraps the socket in a TLSSocket object that
77+
provides the necessary socket and file-like
78+
methods directly, so ``makefile()`` is no longer needed.
6679
"""
6780

6881
@abstractmethod
@@ -110,9 +123,14 @@ def get_environ(self):
110123
"""Return WSGI environ entries to be merged into each request."""
111124
raise NotImplementedError # pragma: no cover
112125

113-
@abstractmethod
114126
def makefile(self, sock, mode='r', bufsize=-1):
115-
"""Return socket file object."""
127+
"""
128+
Return socket file object.
129+
130+
This method is now deprecated. It will be removed in a future version.
131+
132+
:raises NotImplementedError: Must be overridden by subclasses.
133+
"""
116134
raise NotImplementedError # pragma: no cover
117135

118136
def _prompt_for_tls_password(self) -> str:

cheroot/ssl/builtin.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,39 @@
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

3426

27+
# WPS413 is to suppress linter error:
28+
# bad magic module function: __getattr__
29+
# DEFAULT_BUFFER_SIZE is deprecated so this module method will be removed
30+
# in a future release
31+
def __getattr__(name): # noqa: WPS413
32+
if name == 'DEFAULT_BUFFER_SIZE':
33+
_warn(
34+
(
35+
'`DEFAULT_BUFFER_SIZE` is deprecated and '
36+
'will be removed in a future release.'
37+
),
38+
DeprecationWarning,
39+
stacklevel=2,
40+
)
41+
from io import DEFAULT_BUFFER_SIZE
42+
43+
return DEFAULT_BUFFER_SIZE
44+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
45+
46+
3547
def _assert_ssl_exc_contains(exc, *msgs):
3648
"""Check whether SSL exception contains either of messages provided."""
3749
if len(msgs) < 1:
@@ -496,7 +508,19 @@ def _make_env_dn_dict(self, env_prefix, cert_value):
496508
env['%s_%s_%i' % (env_prefix, attr_code, i)] = val
497509
return env
498510

499-
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
500-
"""Return socket file object."""
501-
cls = StreamReader if 'r' in mode else StreamWriter
502-
return cls(sock, mode, bufsize)
511+
def makefile(self, sock, mode='r', bufsize=-1):
512+
"""
513+
Return socket file object.
514+
515+
``makefile`` is now deprecated and will be removed in a future
516+
version.
517+
"""
518+
_warn(
519+
'The `makefile` method is deprecated and will be removed in a future version. '
520+
'The connection socket should be fully wrapped by the adapter '
521+
'before being passed to the HTTPConnection constructor.',
522+
DeprecationWarning,
523+
stacklevel=2,
524+
)
525+
526+
return sock.makefile(mode, bufsize)

cheroot/ssl/builtin.pyi

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

44
from . import Adapter
55

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

0 commit comments

Comments
 (0)