11"""Utilities to manage open connections."""
22
3- import io
3+ import contextlib as _cm
44import os
55import selectors
66import socket
77import threading
88import time
9- from contextlib import suppress
9+ from http import HTTPStatus as _HTTPStatus
10+ from http .client import responses as _http_responses
1011
1112from . import errors
1213from ._compat import IS_WINDOWS
@@ -112,6 +113,16 @@ def close(self):
112113 self ._selector .close ()
113114
114115
116+ @_cm .contextmanager
117+ def _suppress_socket_io_errors (socket , / ):
118+ """Suppress known socket I/O errors."""
119+ try :
120+ yield
121+ except OSError as ex :
122+ if ex .args [0 ] not in errors .socket_errors_to_ignore :
123+ raise
124+
125+
115126class ConnectionManager :
116127 """Class which manages HTTPConnection objects.
117128
@@ -281,7 +292,7 @@ def _remove_invalid_sockets(self):
281292 # One of the reason on why a socket could cause an error
282293 # is that the socket is already closed, ignore the
283294 # socket error if we try to close it at this point.
284- with suppress (OSError ):
295+ with _cm . suppress (OSError ):
285296 conn .close ()
286297
287298 def _from_server_socket (self , server_socket ): # noqa: C901 # FIXME
@@ -297,7 +308,8 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
297308 ssl_env = {}
298309 # if ssl cert and key are set, we try to be a secure HTTP server
299310 if self .server .ssl_adapter is not None :
300- try :
311+ # FIXME: WPS505 -- too many nested blocks
312+ try : # noqa: WPS505
301313 s , ssl_env = self .server .ssl_adapter .wrap (s )
302314 except errors .FatalSSLAlert as tls_connection_drop_error :
303315 self .server .error_log (
@@ -313,23 +325,7 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
313325 'trying to send back a plain HTTP error response: '
314326 f'{ http_over_https_err !s} ' ,
315327 )
316- msg = (
317- 'The client sent a plain HTTP request, but '
318- 'this server only speaks HTTPS on this port.'
319- )
320- buf = [
321- '%s 400 Bad Request\r \n ' % self .server .protocol ,
322- 'Content-Length: %s\r \n ' % len (msg ),
323- 'Content-Type: text/plain\r \n \r \n ' ,
324- msg ,
325- ]
326-
327- wfile = mf (s , 'wb' , io .DEFAULT_BUFFER_SIZE )
328- try :
329- wfile .write ('' .join (buf ).encode ('ISO-8859-1' ))
330- except OSError as ex :
331- if ex .args [0 ] not in errors .socket_errors_to_ignore :
332- raise
328+ self ._send_bad_request_plain_http_error (s )
333329 return None
334330 mf = self .server .ssl_adapter .makefile
335331 # Re-apply our timeout since we may have a new socket object
@@ -403,3 +399,31 @@ def can_add_keepalive_connection(self):
403399 """Flag whether it is allowed to add a new keep-alive connection."""
404400 ka_limit = self .server .keep_alive_conn_limit
405401 return ka_limit is None or self ._num_connections < ka_limit
402+
403+ def _send_bad_request_plain_http_error (self , raw_sock , / ):
404+ """Send Bad Request 400 response, and close the socket."""
405+ msg = (
406+ 'The client sent a plain HTTP request, but this server '
407+ 'only speaks HTTPS on this port.'
408+ )
409+
410+ http_response_status_line = ' ' .join (
411+ (
412+ self .server .protocol ,
413+ str (_HTTPStatus .BAD_REQUEST .value ),
414+ _http_responses [_HTTPStatus .BAD_REQUEST ],
415+ ),
416+ )
417+ response_parts = [
418+ f'{ http_response_status_line } \r \n ' ,
419+ 'Content-Type: text/plain\r \n ' ,
420+ f'Content-Length: { len (msg )} \r \n ' ,
421+ 'Connection: close\r \n ' ,
422+ '\r \n ' ,
423+ msg ,
424+ ]
425+ response_bytes = '' .join (response_parts ).encode ('ISO-8859-1' )
426+
427+ with _suppress_socket_io_errors (raw_sock ), _cm .closing (raw_sock ):
428+ raw_sock .sendall (response_bytes )
429+ raw_sock .shutdown (socket .SHUT_WR )
0 commit comments