moved a bunch of garbage (things that have nothing to do in root) to _trash
This commit is contained in:
parent
d094982b2c
commit
3226ed29ec
2610 changed files with 0 additions and 0 deletions
|
@ -1,388 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
`adafruit_connection_manager`
|
||||
================================================================================
|
||||
|
||||
A urllib3.poolmanager/urllib3.connectionpool-like library for managing sockets and connections
|
||||
|
||||
|
||||
* Author(s): Justin Myers
|
||||
|
||||
Implementation Notes
|
||||
--------------------
|
||||
|
||||
**Software and Dependencies:**
|
||||
|
||||
* Adafruit CircuitPython firmware for the supported boards:
|
||||
https://circuitpython.org/downloads
|
||||
|
||||
"""
|
||||
|
||||
__version__ = "3.1.4"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager.git"
|
||||
|
||||
import errno
|
||||
import sys
|
||||
|
||||
WIZNET5K_SSL_SUPPORT_VERSION = (9, 1)
|
||||
|
||||
if not sys.implementation.name == "circuitpython":
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from circuitpython_typing.socket import (
|
||||
CircuitPythonSocketType,
|
||||
InterfaceType,
|
||||
SocketpoolModuleType,
|
||||
SocketType,
|
||||
SSLContextType,
|
||||
)
|
||||
|
||||
|
||||
class _FakeSSLSocket:
|
||||
def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None:
|
||||
self._socket = socket
|
||||
self._mode = tls_mode
|
||||
self.settimeout = socket.settimeout
|
||||
self.send = socket.send
|
||||
self.recv = socket.recv
|
||||
self.close = socket.close
|
||||
self.recv_into = socket.recv_into
|
||||
# For sockets that come from software socketpools (like the esp32api), they track
|
||||
# the interface and socket pool. We need to make sure the clones do as well
|
||||
self._interface = getattr(socket, "_interface", None)
|
||||
self._socket_pool = getattr(socket, "_socket_pool", None)
|
||||
|
||||
def connect(self, address: Tuple[str, int]) -> None:
|
||||
"""Connect wrapper to add non-standard mode parameter"""
|
||||
try:
|
||||
return self._socket.connect(address, self._mode)
|
||||
except RuntimeError as error:
|
||||
raise OSError(errno.ENOMEM, str(error)) from error
|
||||
|
||||
|
||||
class _FakeSSLContext:
|
||||
def __init__(self, iface: InterfaceType) -> None:
|
||||
self._iface = iface
|
||||
|
||||
def wrap_socket(
|
||||
self, socket: CircuitPythonSocketType, server_hostname: Optional[str] = None
|
||||
) -> _FakeSSLSocket:
|
||||
"""Return the same socket"""
|
||||
if hasattr(self._iface, "TLS_MODE"):
|
||||
return _FakeSSLSocket(socket, self._iface.TLS_MODE)
|
||||
|
||||
raise ValueError("This radio does not support TLS/HTTPS")
|
||||
|
||||
|
||||
def create_fake_ssl_context(
|
||||
socket_pool: SocketpoolModuleType, iface: InterfaceType
|
||||
) -> _FakeSSLContext:
|
||||
"""Method to return a fake SSL context for when ssl isn't available to import
|
||||
|
||||
For example when using a:
|
||||
|
||||
* `Adafruit Ethernet FeatherWing <https://www.adafruit.com/product/3201>`_
|
||||
* `Adafruit AirLift – ESP32 WiFi Co-Processor Breakout Board
|
||||
<https://www.adafruit.com/product/4201>`_
|
||||
* `Adafruit AirLift FeatherWing – ESP32 WiFi Co-Processor
|
||||
<https://www.adafruit.com/product/4264>`_
|
||||
"""
|
||||
if hasattr(socket_pool, "set_interface"):
|
||||
# this is to manually support legacy hardware like the fona
|
||||
socket_pool.set_interface(iface)
|
||||
|
||||
return _FakeSSLContext(iface)
|
||||
|
||||
|
||||
class CPythonNetwork:
|
||||
"""Radio object to use when using ConnectionManager in CPython."""
|
||||
|
||||
|
||||
_global_connection_managers = {}
|
||||
_global_key_by_socketpool = {}
|
||||
_global_socketpools = {}
|
||||
_global_ssl_contexts = {}
|
||||
|
||||
|
||||
def _get_radio_hash_key(radio):
|
||||
try:
|
||||
return hash(radio)
|
||||
except TypeError:
|
||||
return radio.__class__.__name__
|
||||
|
||||
|
||||
def get_radio_socketpool(radio):
|
||||
"""Helper to get a socket pool for common boards.
|
||||
|
||||
Currently supported:
|
||||
|
||||
* Boards with onboard WiFi (ESP32S2, ESP32S3, Pico W, etc)
|
||||
* Using the ESP32 WiFi Co-Processor (like the Adafruit AirLift)
|
||||
* Using a WIZ5500 (Like the Adafruit Ethernet FeatherWing)
|
||||
"""
|
||||
key = _get_radio_hash_key(radio)
|
||||
if key not in _global_socketpools:
|
||||
class_name = radio.__class__.__name__
|
||||
if class_name == "Radio":
|
||||
import ssl
|
||||
|
||||
import socketpool
|
||||
|
||||
pool = socketpool.SocketPool(radio)
|
||||
ssl_context = ssl.create_default_context()
|
||||
|
||||
elif class_name == "ESP_SPIcontrol":
|
||||
import adafruit_esp32spi.adafruit_esp32spi_socketpool as socketpool
|
||||
|
||||
pool = socketpool.SocketPool(radio)
|
||||
ssl_context = create_fake_ssl_context(pool, radio)
|
||||
|
||||
elif class_name == "WIZNET5K":
|
||||
import adafruit_wiznet5k.adafruit_wiznet5k_socketpool as socketpool
|
||||
|
||||
pool = socketpool.SocketPool(radio)
|
||||
|
||||
# Note: At this time, SSL/TLS connections are not supported by older
|
||||
# versions of the Wiznet5k library or on boards withouut the ssl module
|
||||
# see https://docs.circuitpython.org/en/latest/shared-bindings/support_matrix.html
|
||||
ssl_context = None
|
||||
implementation_name = sys.implementation.name
|
||||
implementation_version = sys.implementation.version
|
||||
if (
|
||||
pool.SOCK_STREAM == 1
|
||||
and implementation_name == "circuitpython"
|
||||
and implementation_version >= WIZNET5K_SSL_SUPPORT_VERSION
|
||||
):
|
||||
try:
|
||||
import ssl
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
except ImportError:
|
||||
# if SSL not on board, default to fake_ssl_context
|
||||
pass
|
||||
|
||||
if ssl_context is None:
|
||||
ssl_context = create_fake_ssl_context(pool, radio)
|
||||
|
||||
elif class_name == "CPythonNetwork":
|
||||
import socket as pool
|
||||
import ssl
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported radio class: {class_name}")
|
||||
|
||||
_global_key_by_socketpool[pool] = key
|
||||
_global_socketpools[key] = pool
|
||||
_global_ssl_contexts[key] = ssl_context
|
||||
|
||||
return _global_socketpools[key]
|
||||
|
||||
|
||||
def get_radio_ssl_context(radio):
|
||||
"""Helper to get ssl_contexts for common boards.
|
||||
|
||||
Currently supported:
|
||||
|
||||
* Boards with onboard WiFi (ESP32S2, ESP32S3, Pico W, etc)
|
||||
* Using the ESP32 WiFi Co-Processor (like the Adafruit AirLift)
|
||||
* Using a WIZ5500 (Like the Adafruit Ethernet FeatherWing)
|
||||
"""
|
||||
get_radio_socketpool(radio)
|
||||
return _global_ssl_contexts[_get_radio_hash_key(radio)]
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
"""A library for managing sockets across multiple hardware platforms and libraries."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
socket_pool: SocketpoolModuleType,
|
||||
) -> None:
|
||||
self._socket_pool = socket_pool
|
||||
# Hang onto open sockets so that we can reuse them.
|
||||
self._available_sockets = set()
|
||||
self._key_by_managed_socket = {}
|
||||
self._managed_socket_by_key = {}
|
||||
|
||||
def _free_sockets(self, force: bool = False) -> None:
|
||||
# cloning lists since items are being removed
|
||||
available_sockets = list(self._available_sockets)
|
||||
for socket in available_sockets:
|
||||
self.close_socket(socket)
|
||||
if force:
|
||||
open_sockets = list(self._managed_socket_by_key.values())
|
||||
for socket in open_sockets:
|
||||
self.close_socket(socket)
|
||||
|
||||
def _register_connected_socket(self, key, socket):
|
||||
"""Register a socket as managed."""
|
||||
self._key_by_managed_socket[socket] = key
|
||||
self._managed_socket_by_key[key] = socket
|
||||
|
||||
def _get_connected_socket(
|
||||
self,
|
||||
addr_info: List[Tuple[int, int, int, str, Tuple[str, int]]],
|
||||
host: str,
|
||||
port: int,
|
||||
timeout: float,
|
||||
is_ssl: bool,
|
||||
ssl_context: Optional[SSLContextType] = None,
|
||||
):
|
||||
socket = self._socket_pool.socket(addr_info[0], addr_info[1])
|
||||
|
||||
if is_ssl:
|
||||
socket = ssl_context.wrap_socket(socket, server_hostname=host)
|
||||
connect_host = host
|
||||
else:
|
||||
connect_host = addr_info[-1][0]
|
||||
|
||||
# Set socket read and connect timeout.
|
||||
socket.settimeout(timeout)
|
||||
|
||||
try:
|
||||
socket.connect((connect_host, port))
|
||||
except (MemoryError, OSError):
|
||||
# If any connect problems, clean up and re-raise the problem exception.
|
||||
socket.close()
|
||||
raise
|
||||
|
||||
return socket
|
||||
|
||||
@property
|
||||
def available_socket_count(self) -> int:
|
||||
"""Get the count of available (freed) managed sockets."""
|
||||
return len(self._available_sockets)
|
||||
|
||||
@property
|
||||
def managed_socket_count(self) -> int:
|
||||
"""Get the count of managed sockets."""
|
||||
return len(self._managed_socket_by_key)
|
||||
|
||||
def close_socket(self, socket: SocketType) -> None:
|
||||
"""
|
||||
Close a previously managed and connected socket.
|
||||
|
||||
- **socket_pool** *(SocketType)* – The socket you want to close
|
||||
"""
|
||||
if socket not in self._managed_socket_by_key.values():
|
||||
raise RuntimeError("Socket not managed")
|
||||
socket.close()
|
||||
key = self._key_by_managed_socket.pop(socket)
|
||||
del self._managed_socket_by_key[key]
|
||||
if socket in self._available_sockets:
|
||||
self._available_sockets.remove(socket)
|
||||
|
||||
def free_socket(self, socket: SocketType) -> None:
|
||||
"""Mark a managed socket as available so it can be reused. The socket is not closed."""
|
||||
if socket not in self._managed_socket_by_key.values():
|
||||
raise RuntimeError("Socket not managed")
|
||||
self._available_sockets.add(socket)
|
||||
|
||||
def get_socket(
|
||||
self,
|
||||
host: str,
|
||||
port: int,
|
||||
proto: str,
|
||||
session_id: Optional[str] = None,
|
||||
*,
|
||||
timeout: float = 1.0,
|
||||
is_ssl: bool = False,
|
||||
ssl_context: Optional[SSLContextType] = None,
|
||||
) -> CircuitPythonSocketType:
|
||||
"""
|
||||
Get a new socket and connect to the given host.
|
||||
|
||||
:param str host: host to connect to, such as ``"www.example.org"``
|
||||
:param int port: port to use for connection, such as ``80`` or ``443``
|
||||
:param str proto: connection protocol: ``"http:"``, ``"https:"``, etc.
|
||||
:param Optional[str]: unique session ID,
|
||||
used for multiple simultaneous connections to the same host
|
||||
:param float timeout: how long to wait to connect
|
||||
:param bool is_ssl: ``True`` If the connection is to be over SSL;
|
||||
automatically set when ``proto`` is ``"https:"``
|
||||
:param Optional[SSLContextType]: SSL context to use when making SSL requests
|
||||
"""
|
||||
if session_id:
|
||||
session_id = str(session_id)
|
||||
key = (host, port, proto, session_id)
|
||||
|
||||
# Do we have already have a socket available for the requested connection?
|
||||
if key in self._managed_socket_by_key:
|
||||
socket = self._managed_socket_by_key[key]
|
||||
if socket in self._available_sockets:
|
||||
self._available_sockets.remove(socket)
|
||||
return socket
|
||||
|
||||
raise RuntimeError(f"An existing socket is already connected to {proto}//{host}:{port}")
|
||||
|
||||
if proto == "https:":
|
||||
is_ssl = True
|
||||
if is_ssl and not ssl_context:
|
||||
raise ValueError("ssl_context must be provided if using ssl")
|
||||
|
||||
addr_info = self._socket_pool.getaddrinfo(host, port, 0, self._socket_pool.SOCK_STREAM)[0]
|
||||
|
||||
try:
|
||||
socket = self._get_connected_socket(addr_info, host, port, timeout, is_ssl, ssl_context)
|
||||
self._register_connected_socket(key, socket)
|
||||
return socket
|
||||
except (MemoryError, OSError, RuntimeError):
|
||||
# Could not get a new socket (or two, if SSL).
|
||||
# If there are any available sockets, free them all and try again.
|
||||
if self.available_socket_count:
|
||||
self._free_sockets()
|
||||
socket = self._get_connected_socket(
|
||||
addr_info, host, port, timeout, is_ssl, ssl_context
|
||||
)
|
||||
self._register_connected_socket(key, socket)
|
||||
return socket
|
||||
# Re-raise exception if no sockets could be freed.
|
||||
raise
|
||||
|
||||
|
||||
def connection_manager_close_all(
|
||||
socket_pool: Optional[SocketpoolModuleType] = None, release_references: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Close all open sockets for pool, optionally release references.
|
||||
|
||||
:param Optional[SocketpoolModuleType] socket_pool:
|
||||
a specific socket pool whose sockets you want to close; ``None`` means all socket pools
|
||||
:param bool release_references: ``True`` if you also want the `ConnectionManager` to forget
|
||||
all the socket pools and SSL contexts it knows about
|
||||
"""
|
||||
if socket_pool:
|
||||
socket_pools = [socket_pool]
|
||||
else:
|
||||
socket_pools = _global_connection_managers.keys()
|
||||
|
||||
for pool in socket_pools:
|
||||
connection_manager = _global_connection_managers.get(pool, None)
|
||||
if connection_manager is None:
|
||||
raise RuntimeError("SocketPool not managed")
|
||||
|
||||
connection_manager._free_sockets(force=True)
|
||||
|
||||
if not release_references:
|
||||
continue
|
||||
|
||||
key = _global_key_by_socketpool.pop(pool)
|
||||
if key:
|
||||
_global_socketpools.pop(key, None)
|
||||
_global_ssl_contexts.pop(key, None)
|
||||
|
||||
_global_connection_managers.pop(pool, None)
|
||||
|
||||
|
||||
def get_connection_manager(socket_pool: SocketpoolModuleType) -> ConnectionManager:
|
||||
"""
|
||||
Get or create the ConnectionManager singleton for the given pool.
|
||||
"""
|
||||
if socket_pool not in _global_connection_managers:
|
||||
_global_connection_managers[socket_pool] = ConnectionManager(socket_pool)
|
||||
return _global_connection_managers[socket_pool]
|
Loading…
Add table
Add a link
Reference in a new issue