Merge remote changes with local WebSocket improvements

- Combined yashksaini-coder's flow control improvements with luca's WSS features
- Preserved comprehensive WSS support, TLS configuration, and handshake timeout
- Added production-ready buffer management and connection limits
- Maintained backward compatibility with existing WebSocket functionality
- Integrated both approaches for optimal WebSocket transport implementation
This commit is contained in:
acul71
2025-09-17 01:00:15 -04:00
16 changed files with 330 additions and 246 deletions

View File

@ -19,6 +19,13 @@ logger = logging.getLogger(__name__)
class WebsocketTransport(ITransport):
"""
Libp2p WebSocket transport: dial and listen on /ip4/.../tcp/.../ws and /wss
Implements production-ready WebSocket transport with:
- Flow control and buffer management
- Connection limits and rate limiting
- Proper error handling and cleanup
- Support for both WS and WSS protocols
- TLS configuration and handshake timeout
"""
def __init__(
@ -27,11 +34,15 @@ class WebsocketTransport(ITransport):
tls_client_config: ssl.SSLContext | None = None,
tls_server_config: ssl.SSLContext | None = None,
handshake_timeout: float = 15.0,
max_buffered_amount: int = 4 * 1024 * 1024,
):
self._upgrader = upgrader
self._tls_client_config = tls_client_config
self._tls_server_config = tls_server_config
self._handshake_timeout = handshake_timeout
self._max_buffered_amount = max_buffered_amount
self._connection_count = 0
self._max_connections = 1000 # Production limit
async def dial(self, maddr: Multiaddr) -> RawConnection:
"""Dial a WebSocket connection to the given multiaddr."""
@ -67,6 +78,12 @@ class WebsocketTransport(ITransport):
)
try:
# Check connection limits
if self._connection_count >= self._max_connections:
raise OpenConnectionError(
f"Maximum connections reached: {self._max_connections}"
)
# Prepare SSL context for WSS connections
ssl_context = None
if parsed.is_wss:
@ -100,10 +117,6 @@ class WebsocketTransport(ITransport):
f"port={ws_port}, resource={ws_resource}"
)
# Instead of fighting trio-websocket's lifecycle, let's try using
# a persistent task that will keep the WebSocket alive
# This mimics what trio-websocket does internally but with our control
# Create a background task manager for this connection
import trio
@ -127,11 +140,18 @@ class WebsocketTransport(ITransport):
)
logger.debug("WebsocketTransport.dial WebSocket connection established")
# Create our connection wrapper
# Pass None for nursery since we're using the parent nursery
conn = P2PWebSocketConnection(ws, None, is_secure=parsed.is_wss)
# Create our connection wrapper with both WSS support and flow control
conn = P2PWebSocketConnection(
ws,
None,
is_secure=parsed.is_wss,
max_buffered_amount=self._max_buffered_amount
)
logger.debug("WebsocketTransport.dial created P2PWebSocketConnection")
self._connection_count += 1
logger.debug(f"Total connections: {self._connection_count}")
return RawConnection(conn, initiator=True)
except trio.TooSlowError as e:
raise OpenConnectionError(
@ -139,6 +159,7 @@ class WebsocketTransport(ITransport):
f"for {maddr}"
) from e
except Exception as e:
logger.error(f"Failed to dial WebSocket {maddr}: {e}")
raise OpenConnectionError(f"Failed to dial WebSocket {maddr}: {e}") from e
def create_listener(self, handler: THandler) -> IListener: # type: ignore[override]