15
15
from ..http11 import USER_AGENT , Response
16
16
from ..protocol import CONNECTING , Event
17
17
from ..typing import LoggerLike , Origin , Subprotocol
18
- from ..uri import Proxy , get_proxy , parse_proxy , parse_uri
18
+ from ..uri import Proxy , WebSocketURI , get_proxy , parse_proxy , parse_uri
19
19
from .connection import Connection
20
20
from .utils import Deadline
21
21
@@ -258,15 +258,12 @@ def connect(
258
258
elif compression is not None :
259
259
raise ValueError (f"unsupported compression: { compression } " )
260
260
261
- proxy_uri : Proxy | None = None
262
261
if unix :
263
262
proxy = None
264
263
if sock is not None :
265
264
proxy = None
266
265
if proxy is True :
267
266
proxy = get_proxy (ws_uri )
268
- if proxy is not None :
269
- proxy_uri = parse_proxy (proxy )
270
267
271
268
# Calculate timeouts on the TCP, TLS, and WebSocket handshakes.
272
269
# The TCP and TLS timeouts must be set on the socket, then removed
@@ -285,54 +282,21 @@ def connect(
285
282
sock .settimeout (deadline .timeout ())
286
283
assert path is not None # mypy cannot figure this out
287
284
sock .connect (path )
285
+ elif proxy is not None :
286
+ sock = connect_proxy (
287
+ parse_proxy (proxy ),
288
+ ws_uri ,
289
+ deadline ,
290
+ # websockets is consistent with the socket module while
291
+ # python_socks is consistent across implementations.
292
+ local_addr = kwargs .pop ("source_address" , None ),
293
+ )
288
294
else :
289
- if proxy_uri is not None :
290
- if proxy_uri .scheme [:5 ] == "socks" :
291
- try :
292
- from python_socks import ProxyType
293
- from python_socks .sync import Proxy
294
- except ImportError :
295
- raise ImportError (
296
- "python-socks is required to use a SOCKS proxy"
297
- )
298
- if proxy_uri .scheme == "socks5h" :
299
- proxy_type = ProxyType .SOCKS5
300
- rdns = True
301
- elif proxy_uri .scheme == "socks5" :
302
- proxy_type = ProxyType .SOCKS5
303
- rdns = False
304
- # We use mitmproxy for testing and it doesn't support SOCKS4.
305
- elif proxy_uri .scheme == "socks4a" : # pragma: no cover
306
- proxy_type = ProxyType .SOCKS4
307
- rdns = True
308
- elif proxy_uri .scheme == "socks4" : # pragma: no cover
309
- proxy_type = ProxyType .SOCKS4
310
- rdns = False
311
- # Proxy types are enforced in parse_proxy().
312
- else :
313
- raise AssertionError ("unsupported SOCKS proxy" )
314
- socks_proxy = Proxy (
315
- proxy_type ,
316
- proxy_uri .host ,
317
- proxy_uri .port ,
318
- proxy_uri .username ,
319
- proxy_uri .password ,
320
- rdns ,
321
- )
322
- sock = socks_proxy .connect (
323
- ws_uri .host ,
324
- ws_uri .port ,
325
- timeout = deadline .timeout (),
326
- local_addr = kwargs .pop ("local_addr" , None ),
327
- )
328
- # Proxy types are enforced in parse_proxy().
329
- else :
330
- raise AssertionError ("unsupported proxy" )
331
- else :
332
- kwargs .setdefault ("timeout" , deadline .timeout ())
333
- sock = socket .create_connection (
334
- (ws_uri .host , ws_uri .port ), ** kwargs
335
- )
295
+ kwargs .setdefault ("timeout" , deadline .timeout ())
296
+ sock = socket .create_connection (
297
+ (ws_uri .host , ws_uri .port ),
298
+ ** kwargs ,
299
+ )
336
300
sock .settimeout (None )
337
301
338
302
# Disable Nagle algorithm
@@ -420,3 +384,64 @@ def unix_connect(
420
384
else :
421
385
uri = "wss://localhost/"
422
386
return connect (uri = uri , unix = True , path = path , ** kwargs )
387
+
388
+
389
+ try :
390
+ from python_socks import ProxyType
391
+ from python_socks .sync import Proxy as SocksProxy
392
+
393
+ SOCKS_PROXY_TYPES = {
394
+ "socks5h" : ProxyType .SOCKS5 ,
395
+ "socks5" : ProxyType .SOCKS5 ,
396
+ "socks4a" : ProxyType .SOCKS4 ,
397
+ "socks4" : ProxyType .SOCKS4 ,
398
+ }
399
+
400
+ SOCKS_PROXY_RDNS = {
401
+ "socks5h" : True ,
402
+ "socks5" : False ,
403
+ "socks4a" : True ,
404
+ "socks4" : False ,
405
+ }
406
+
407
+ def connect_socks_proxy (
408
+ proxy : Proxy ,
409
+ ws_uri : WebSocketURI ,
410
+ deadline : Deadline ,
411
+ ** kwargs : Any ,
412
+ ) -> socket .socket :
413
+ """Connect via a SOCKS proxy and return the socket."""
414
+ socks_proxy = SocksProxy (
415
+ SOCKS_PROXY_TYPES [proxy .scheme ],
416
+ proxy .host ,
417
+ proxy .port ,
418
+ proxy .username ,
419
+ proxy .password ,
420
+ SOCKS_PROXY_RDNS [proxy .scheme ],
421
+ )
422
+ kwargs .setdefault ("timeout" , deadline .timeout ())
423
+ return socks_proxy .connect (ws_uri .host , ws_uri .port , ** kwargs )
424
+
425
+ except ImportError :
426
+
427
+ def connect_socks_proxy (
428
+ proxy : Proxy ,
429
+ ws_uri : WebSocketURI ,
430
+ deadline : Deadline ,
431
+ ** kwargs : Any ,
432
+ ) -> socket .socket :
433
+ raise ImportError ("python-socks is required to use a SOCKS proxy" )
434
+
435
+
436
+ def connect_proxy (
437
+ proxy : Proxy ,
438
+ ws_uri : WebSocketURI ,
439
+ deadline : Deadline ,
440
+ ** kwargs : Any ,
441
+ ) -> socket .socket :
442
+ """Connect via a proxy and return the socket."""
443
+ # parse_proxy() validates proxy.scheme.
444
+ if proxy .scheme [:5 ] == "socks" :
445
+ return connect_socks_proxy (proxy , ws_uri , deadline , ** kwargs )
446
+ else :
447
+ raise AssertionError ("unsupported proxy" )
0 commit comments