7
7
import urllib .parse
8
8
from collections .abc import AsyncIterator , Generator , Sequence
9
9
from types import TracebackType
10
- from typing import Any , Callable
10
+ from typing import Any , Callable , Literal
11
11
12
12
from ..client import ClientProtocol , backoff
13
13
from ..datastructures import HeadersLike
18
18
from ..http11 import USER_AGENT , Response
19
19
from ..protocol import CONNECTING , Event
20
20
from ..typing import LoggerLike , Origin , Subprotocol
21
- from ..uri import WebSocketURI , parse_uri
21
+ from ..uri import ProxyURI , WebSocketURI , get_proxy , parse_proxy_uri , parse_uri
22
22
from .compatibility import TimeoutError , asyncio_timeout
23
23
from .connection import Connection
24
24
@@ -208,6 +208,10 @@ class connect:
208
208
user_agent_header: Value of the ``User-Agent`` request header.
209
209
It defaults to ``"Python/x.y.z websockets/X.Y"``.
210
210
Setting it to :obj:`None` removes the header.
211
+ proxy: If a proxy is configured, it is used by default. Set ``proxy``
212
+ to :obj:`None` to disable the proxy or to the address of a proxy
213
+ to override the system configuration. See the :doc:`proxy docs
214
+ <../../topics/proxy>` for details.
211
215
process_exception: When reconnecting automatically, tell whether an
212
216
error is transient or fatal. The default behavior is defined by
213
217
:func:`process_exception`. Refer to its documentation for details.
@@ -279,6 +283,7 @@ def __init__(
279
283
# HTTP
280
284
additional_headers : HeadersLike | None = None ,
281
285
user_agent_header : str | None = USER_AGENT ,
286
+ proxy : str | Literal [True ] | None = True ,
282
287
process_exception : Callable [[Exception ], Exception | None ] = process_exception ,
283
288
# Timeouts
284
289
open_timeout : float | None = 10 ,
@@ -333,6 +338,7 @@ def protocol_factory(uri: WebSocketURI) -> ClientConnection:
333
338
)
334
339
return connection
335
340
341
+ self .proxy = proxy
336
342
self .protocol_factory = protocol_factory
337
343
self .handshake_args = (
338
344
additional_headers ,
@@ -346,9 +352,20 @@ def protocol_factory(uri: WebSocketURI) -> ClientConnection:
346
352
async def create_connection (self ) -> ClientConnection :
347
353
"""Create TCP or Unix connection."""
348
354
loop = asyncio .get_running_loop ()
355
+ kwargs = self .connection_kwargs .copy ()
349
356
350
357
ws_uri = parse_uri (self .uri )
351
- kwargs = self .connection_kwargs .copy ()
358
+
359
+ proxy = self .proxy
360
+ proxy_uri : ProxyURI | None = None
361
+ if kwargs .pop ("unix" , False ):
362
+ proxy = None
363
+ if kwargs .get ("sock" ) is not None :
364
+ proxy = None
365
+ if proxy is True :
366
+ proxy = get_proxy (ws_uri )
367
+ if proxy is not None :
368
+ proxy_uri = parse_proxy_uri (proxy )
352
369
353
370
def factory () -> ClientConnection :
354
371
return self .protocol_factory (ws_uri )
@@ -365,6 +382,38 @@ def factory() -> ClientConnection:
365
382
if kwargs .pop ("unix" , False ):
366
383
_ , connection = await loop .create_unix_connection (factory , ** kwargs )
367
384
else :
385
+ if proxy_uri is not None :
386
+ if proxy_uri .scheme [:5 ] == "socks" :
387
+ try :
388
+ from python_socks import ProxyType
389
+ from python_socks .async_ .asyncio import Proxy
390
+ except ImportError :
391
+ raise ImportError (
392
+ "python-socks is required to use a SOCKS proxy"
393
+ )
394
+ if proxy_uri .scheme [:6 ] == "socks5" :
395
+ proxy_type = ProxyType .SOCKS5
396
+ elif proxy_uri .scheme [:6 ] == "socks4" :
397
+ proxy_type = ProxyType .SOCKS4
398
+ else :
399
+ raise AssertionError ("unsupported SOCKS proxy" )
400
+ socks_proxy = Proxy (
401
+ proxy_type ,
402
+ proxy_uri .host ,
403
+ proxy_uri .port ,
404
+ proxy_uri .username ,
405
+ proxy_uri .password ,
406
+ rdns = kwargs .pop ("rdns" , None ),
407
+ )
408
+ kwargs ["sock" ] = await socks_proxy .connect (
409
+ ws_uri .host ,
410
+ ws_uri .port ,
411
+ local_addr = kwargs .pop ("local_addr" , None ),
412
+ )
413
+ else :
414
+ raise NotImplementedError (
415
+ f"proxy scheme not implemented yet: { proxy_uri .scheme } "
416
+ )
368
417
if kwargs .get ("sock" ) is None :
369
418
kwargs .setdefault ("host" , ws_uri .host )
370
419
kwargs .setdefault ("port" , ws_uri .port )
0 commit comments