annotate urllib3/util/connection.py @ 13:f550715358f1

planemo upload for repository https://toolrepo.galaxytrakr.org/view/jpayne/bioproject_to_srr_2/556cac4fb538
author jpayne
date Mon, 20 May 2024 00:56:52 -0400
parents 5eb2d5e3bf22
children
rev   line source
jpayne@7 1 from __future__ import annotations
jpayne@7 2
jpayne@7 3 import socket
jpayne@7 4 import typing
jpayne@7 5
jpayne@7 6 from ..exceptions import LocationParseError
jpayne@7 7 from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT
jpayne@7 8
jpayne@7 9 _TYPE_SOCKET_OPTIONS = typing.Sequence[typing.Tuple[int, int, typing.Union[int, bytes]]]
jpayne@7 10
jpayne@7 11 if typing.TYPE_CHECKING:
jpayne@7 12 from .._base_connection import BaseHTTPConnection
jpayne@7 13
jpayne@7 14
jpayne@7 15 def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific
jpayne@7 16 """
jpayne@7 17 Returns True if the connection is dropped and should be closed.
jpayne@7 18 :param conn: :class:`urllib3.connection.HTTPConnection` object.
jpayne@7 19 """
jpayne@7 20 return not conn.is_connected
jpayne@7 21
jpayne@7 22
jpayne@7 23 # This function is copied from socket.py in the Python 2.7 standard
jpayne@7 24 # library test suite. Added to its signature is only `socket_options`.
jpayne@7 25 # One additional modification is that we avoid binding to IPv6 servers
jpayne@7 26 # discovered in DNS if the system doesn't have IPv6 functionality.
jpayne@7 27 def create_connection(
jpayne@7 28 address: tuple[str, int],
jpayne@7 29 timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
jpayne@7 30 source_address: tuple[str, int] | None = None,
jpayne@7 31 socket_options: _TYPE_SOCKET_OPTIONS | None = None,
jpayne@7 32 ) -> socket.socket:
jpayne@7 33 """Connect to *address* and return the socket object.
jpayne@7 34
jpayne@7 35 Convenience function. Connect to *address* (a 2-tuple ``(host,
jpayne@7 36 port)``) and return the socket object. Passing the optional
jpayne@7 37 *timeout* parameter will set the timeout on the socket instance
jpayne@7 38 before attempting to connect. If no *timeout* is supplied, the
jpayne@7 39 global default timeout setting returned by :func:`socket.getdefaulttimeout`
jpayne@7 40 is used. If *source_address* is set it must be a tuple of (host, port)
jpayne@7 41 for the socket to bind as a source address before making the connection.
jpayne@7 42 An host of '' or port 0 tells the OS to use the default.
jpayne@7 43 """
jpayne@7 44
jpayne@7 45 host, port = address
jpayne@7 46 if host.startswith("["):
jpayne@7 47 host = host.strip("[]")
jpayne@7 48 err = None
jpayne@7 49
jpayne@7 50 # Using the value from allowed_gai_family() in the context of getaddrinfo lets
jpayne@7 51 # us select whether to work with IPv4 DNS records, IPv6 records, or both.
jpayne@7 52 # The original create_connection function always returns all records.
jpayne@7 53 family = allowed_gai_family()
jpayne@7 54
jpayne@7 55 try:
jpayne@7 56 host.encode("idna")
jpayne@7 57 except UnicodeError:
jpayne@7 58 raise LocationParseError(f"'{host}', label empty or too long") from None
jpayne@7 59
jpayne@7 60 for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
jpayne@7 61 af, socktype, proto, canonname, sa = res
jpayne@7 62 sock = None
jpayne@7 63 try:
jpayne@7 64 sock = socket.socket(af, socktype, proto)
jpayne@7 65
jpayne@7 66 # If provided, set socket level options before connecting.
jpayne@7 67 _set_socket_options(sock, socket_options)
jpayne@7 68
jpayne@7 69 if timeout is not _DEFAULT_TIMEOUT:
jpayne@7 70 sock.settimeout(timeout)
jpayne@7 71 if source_address:
jpayne@7 72 sock.bind(source_address)
jpayne@7 73 sock.connect(sa)
jpayne@7 74 # Break explicitly a reference cycle
jpayne@7 75 err = None
jpayne@7 76 return sock
jpayne@7 77
jpayne@7 78 except OSError as _:
jpayne@7 79 err = _
jpayne@7 80 if sock is not None:
jpayne@7 81 sock.close()
jpayne@7 82
jpayne@7 83 if err is not None:
jpayne@7 84 try:
jpayne@7 85 raise err
jpayne@7 86 finally:
jpayne@7 87 # Break explicitly a reference cycle
jpayne@7 88 err = None
jpayne@7 89 else:
jpayne@7 90 raise OSError("getaddrinfo returns an empty list")
jpayne@7 91
jpayne@7 92
jpayne@7 93 def _set_socket_options(
jpayne@7 94 sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None
jpayne@7 95 ) -> None:
jpayne@7 96 if options is None:
jpayne@7 97 return
jpayne@7 98
jpayne@7 99 for opt in options:
jpayne@7 100 sock.setsockopt(*opt)
jpayne@7 101
jpayne@7 102
jpayne@7 103 def allowed_gai_family() -> socket.AddressFamily:
jpayne@7 104 """This function is designed to work in the context of
jpayne@7 105 getaddrinfo, where family=socket.AF_UNSPEC is the default and
jpayne@7 106 will perform a DNS search for both IPv6 and IPv4 records."""
jpayne@7 107
jpayne@7 108 family = socket.AF_INET
jpayne@7 109 if HAS_IPV6:
jpayne@7 110 family = socket.AF_UNSPEC
jpayne@7 111 return family
jpayne@7 112
jpayne@7 113
jpayne@7 114 def _has_ipv6(host: str) -> bool:
jpayne@7 115 """Returns True if the system can bind an IPv6 address."""
jpayne@7 116 sock = None
jpayne@7 117 has_ipv6 = False
jpayne@7 118
jpayne@7 119 if socket.has_ipv6:
jpayne@7 120 # has_ipv6 returns true if cPython was compiled with IPv6 support.
jpayne@7 121 # It does not tell us if the system has IPv6 support enabled. To
jpayne@7 122 # determine that we must bind to an IPv6 address.
jpayne@7 123 # https://github.com/urllib3/urllib3/pull/611
jpayne@7 124 # https://bugs.python.org/issue658327
jpayne@7 125 try:
jpayne@7 126 sock = socket.socket(socket.AF_INET6)
jpayne@7 127 sock.bind((host, 0))
jpayne@7 128 has_ipv6 = True
jpayne@7 129 except Exception:
jpayne@7 130 pass
jpayne@7 131
jpayne@7 132 if sock:
jpayne@7 133 sock.close()
jpayne@7 134 return has_ipv6
jpayne@7 135
jpayne@7 136
jpayne@7 137 HAS_IPV6 = _has_ipv6("::1")