mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-06-06 05:30:39 +02:00
feat: adds support for services behind a SOCKS5 proxy
This commit is contained in:
parent
3d65240784
commit
18c66a8318
@ -6,6 +6,8 @@ import urllib.parse
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
||||
|
||||
import aiohttp
|
||||
import aiohttp_socks.connector
|
||||
import socketio # type: ignore
|
||||
|
||||
from pydase.client.proxy_class import ProxyClass
|
||||
@ -40,47 +42,52 @@ def asyncio_loop_thread(loop: asyncio.AbstractEventLoop) -> None:
|
||||
|
||||
|
||||
class Client:
|
||||
"""
|
||||
A client for connecting to a remote pydase service using socket.io. This client
|
||||
"""A client for connecting to a remote pydase service using Socket.IO. This client
|
||||
handles asynchronous communication with a service, manages events such as
|
||||
connection, disconnection, and updates, and ensures that the proxy object is
|
||||
up-to-date with the server state.
|
||||
|
||||
Args:
|
||||
url:
|
||||
The URL of the pydase Socket.IO server. This should always contain the
|
||||
protocol and the hostname.
|
||||
block_until_connected:
|
||||
If set to True, the constructor will block until the connection to the
|
||||
service has been established. This is useful for ensuring the client is
|
||||
ready to use immediately after instantiation. Default is True.
|
||||
sio_client_kwargs:
|
||||
Additional keyword arguments passed to the underlying
|
||||
url: The URL of the pydase Socket.IO server. This should always contain the
|
||||
protocol (e.g., `ws` or `wss`) and the hostname, and can optionally include
|
||||
a path prefix (e.g., `ws://localhost:8001/service`).
|
||||
block_until_connected: If set to True, the constructor will block until the
|
||||
connection to the service has been established. This is useful for ensuring
|
||||
the client is ready to use immediately after instantiation. Default is True.
|
||||
sio_client_kwargs: Additional keyword arguments passed to the underlying
|
||||
[`AsyncClient`][socketio.AsyncClient]. This allows fine-tuning of the
|
||||
client's behaviour (e.g., reconnection attempts or reconnection delay).
|
||||
Default is an empty dictionary.
|
||||
client_id: Client identification that will be shown in the server logs this
|
||||
client is connecting to. This ID is passed as a `X-Client-Id` header in the
|
||||
HTTP(s) request. Defaults to None.
|
||||
client_id: An optional client identifier. This ID is sent to the server as the
|
||||
`X-Client-Id` HTTP header. It can be used for logging or authentication
|
||||
purposes on the server side.
|
||||
proxy_url: An optional proxy URL to route the connection through. This is useful
|
||||
if the service is only reachable via an SSH tunnel or behind a firewall
|
||||
(e.g., `socks5://localhost:2222`).
|
||||
|
||||
Example:
|
||||
The following example demonstrates a `Client` instance that connects to another
|
||||
pydase service, while customising some of the connection settings for the
|
||||
underlying [`AsyncClient`][socketio.AsyncClient].
|
||||
Connect to a service directly:
|
||||
|
||||
```python
|
||||
pydase.Client(url="ws://localhost:8001", sio_client_kwargs={
|
||||
"reconnection_attempts": 2,
|
||||
"reconnection_delay": 2,
|
||||
"reconnection_delay_max": 8,
|
||||
})
|
||||
client = pydase.Client(url="ws://localhost:8001")
|
||||
```
|
||||
|
||||
When connecting to a server over a secure connection (i.e., the server is using
|
||||
SSL/TLS encryption), make sure that the `wss` protocol is used instead of `ws`:
|
||||
Connect over a secure connection:
|
||||
|
||||
```python
|
||||
pydase.Client(url="wss://my-service.example.com")
|
||||
client = pydase.Client(url="wss://my-service.example.com")
|
||||
```
|
||||
|
||||
Connect using a SOCKS5 proxy (e.g., through an SSH tunnel):
|
||||
|
||||
```bash
|
||||
ssh -D 2222 user@gateway.example.com
|
||||
```
|
||||
|
||||
```python
|
||||
client = pydase.Client(
|
||||
url="ws://remote-server:8001",
|
||||
proxy_url="socks5://localhost:2222"
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
@ -91,6 +98,7 @@ class Client:
|
||||
block_until_connected: bool = True,
|
||||
sio_client_kwargs: dict[str, Any] = {},
|
||||
client_id: str | None = None,
|
||||
proxy_url: str | None = None,
|
||||
):
|
||||
# Parse the URL to separate base URL and path prefix
|
||||
parsed_url = urllib.parse.urlparse(url)
|
||||
@ -103,8 +111,9 @@ class Client:
|
||||
# Store the path prefix (e.g., "/service" in "ws://localhost:8081/service")
|
||||
self._path_prefix = parsed_url.path.rstrip("/") # Remove trailing slash if any
|
||||
self._url = url
|
||||
self._sio = socketio.AsyncClient(**sio_client_kwargs)
|
||||
self._proxy_url = proxy_url
|
||||
self._client_id = client_id
|
||||
self._sio_client_kwargs = sio_client_kwargs
|
||||
self._loop: asyncio.AbstractEventLoop | None = None
|
||||
self._thread: threading.Thread | None = None
|
||||
self.proxy: ProxyClass
|
||||
@ -125,6 +134,19 @@ class Client:
|
||||
|
||||
def connect(self, block_until_connected: bool = True) -> None:
|
||||
if self._thread is None or self._loop is None:
|
||||
if self._proxy_url is not None:
|
||||
session = aiohttp.ClientSession(
|
||||
connector=aiohttp_socks.connector.ProxyConnector.from_url(
|
||||
url=self._proxy_url, loop=self._loop
|
||||
),
|
||||
loop=self._loop,
|
||||
)
|
||||
self._sio = socketio.AsyncClient(
|
||||
http_session=session, **self._sio_client_kwargs
|
||||
)
|
||||
else:
|
||||
self._sio = socketio.AsyncClient(**self._sio_client_kwargs)
|
||||
|
||||
self._loop = self._initialize_loop_and_thread()
|
||||
|
||||
connection_future = asyncio.run_coroutine_threadsafe(
|
||||
|
Loading…
x
Reference in New Issue
Block a user