|
67 | 67 |
|
68 | 68 | import contextlib
|
69 | 69 | import email.utils
|
| 70 | +import errno |
70 | 71 | import io
|
71 | 72 | import logging
|
72 | 73 | import os
|
|
81 | 82 | import urllib.parse
|
82 | 83 | from functools import lru_cache
|
83 | 84 |
|
| 85 | +from OpenSSL.SSL import SysCallError |
| 86 | + |
| 87 | +from cheroot.errors import ( |
| 88 | + acceptable_sock_shutdown_error_codes, |
| 89 | + acceptable_sock_shutdown_exceptions, |
| 90 | +) |
| 91 | + |
84 | 92 | from . import __version__, connections, errors
|
85 | 93 | from ._compat import IS_PPC, bton
|
86 | 94 | from .makefile import MakeFile, StreamWriter
|
@@ -1189,9 +1197,21 @@ def write(self, chunk):
|
1189 | 1197 | if self.chunked_write and chunk:
|
1190 | 1198 | chunk_size_hex = hex(len(chunk))[2:].encode('ascii')
|
1191 | 1199 | buf = [chunk_size_hex, CRLF, chunk, CRLF]
|
1192 |
| - self.conn.wfile.write(EMPTY.join(buf)) |
| 1200 | + data = EMPTY.join(buf) |
1193 | 1201 | else:
|
1194 |
| - self.conn.wfile.write(chunk) |
| 1202 | + data = chunk |
| 1203 | + |
| 1204 | + try: |
| 1205 | + self.conn.wfile.write(data) |
| 1206 | + except (SysCallError, ConnectionError, OSError) as write_error: |
| 1207 | + if isinstance(write_error, OSError): |
| 1208 | + error_code = write_error.errno |
| 1209 | + else: |
| 1210 | + error_code = write_error.args[0] |
| 1211 | + if error_code in acceptable_sock_shutdown_error_codes: |
| 1212 | + # The socket is gone, so just ignore this error. |
| 1213 | + return |
| 1214 | + raise |
1195 | 1215 |
|
1196 | 1216 | def send_headers(self): # noqa: C901 # FIXME
|
1197 | 1217 | """Assert, process, and send the HTTP response message-headers.
|
@@ -1285,7 +1305,27 @@ def send_headers(self): # noqa: C901 # FIXME
|
1285 | 1305 | for k, v in self.outheaders:
|
1286 | 1306 | buf.append(k + COLON + SPACE + v + CRLF)
|
1287 | 1307 | buf.append(CRLF)
|
1288 |
| - self.conn.wfile.write(EMPTY.join(buf)) |
| 1308 | + try: |
| 1309 | + self.conn.wfile.write(EMPTY.join(buf)) |
| 1310 | + except (SysCallError, ConnectionError, OSError) as e: |
| 1311 | + # We explicitly ignore these errors because they indicate the |
| 1312 | + # client has already closed the connection, which is a normal |
| 1313 | + # occurrence during a race condition. |
| 1314 | + |
| 1315 | + # The .errno attribute is only available on OSError |
| 1316 | + # The .args[0] attribute is available on SysCallError |
| 1317 | + # Check for both cases to handle different exception types |
| 1318 | + error_code = e.errno if isinstance(e, OSError) else e.args[0] |
| 1319 | + if error_code in { |
| 1320 | + errno.ECONNRESET, |
| 1321 | + errno.EPIPE, |
| 1322 | + errno.ENOTCONN, |
| 1323 | + errno.EBADF, |
| 1324 | + }: |
| 1325 | + self.close_connection = True |
| 1326 | + self.conn.close() |
| 1327 | + return |
| 1328 | + raise |
1289 | 1329 |
|
1290 | 1330 |
|
1291 | 1331 | class HTTPConnection:
|
@@ -1543,10 +1583,10 @@ def _close_kernel_socket(self):
|
1543 | 1583 |
|
1544 | 1584 | try:
|
1545 | 1585 | shutdown(socket.SHUT_RDWR) # actually send a TCP FIN
|
1546 |
| - except errors.acceptable_sock_shutdown_exceptions: |
| 1586 | + except acceptable_sock_shutdown_exceptions: # pylint: disable=E0712 |
1547 | 1587 | pass
|
1548 | 1588 | except socket.error as e:
|
1549 |
| - if e.errno not in errors.acceptable_sock_shutdown_error_codes: |
| 1589 | + if e.errno not in acceptable_sock_shutdown_error_codes: |
1550 | 1590 | raise
|
1551 | 1591 |
|
1552 | 1592 |
|
|
0 commit comments