1 from __future__
import annotations
8 from ._helper
import create_connection
, select_proxy
, make_socks_proxy_opts
, create_socks_proxy_socket
9 from .common
import Response
, register_rh
, Features
10 from .exceptions
import (
11 CertificateVerifyError
,
15 TransportError
, ProxyError
,
17 from .websocket
import WebSocketRequestHandler
, WebSocketResponse
18 from ..compat
import functools
19 from ..dependencies
import websockets
20 from ..utils
import int_or_none
21 from ..socks
import ProxyError
as SocksProxyError
24 raise ImportError('websockets is not installed')
26 import websockets
.version
28 websockets_version
= tuple(map(int_or_none
, websockets
.version
.version
.split('.')))
29 if websockets_version
< (12, 0):
30 raise ImportError('Only websockets>=12.0 is supported')
32 import websockets
.sync
.client
33 from websockets
.uri
import parse_uri
36 class WebsocketsResponseAdapter(WebSocketResponse
):
38 def __init__(self
, wsw
: websockets
.sync
.client
.ClientConnection
, url
):
40 fp
=io
.BytesIO(wsw
.response
.body
or b
''),
42 headers
=wsw
.response
.headers
,
43 status
=wsw
.response
.status_code
,
44 reason
=wsw
.response
.reason_phrase
,
52 def send(self
, message
):
53 # https://websockets.readthedocs.io/en/stable/reference/sync/client.html#websockets.sync.client.ClientConnection.send
55 return self
.wsw
.send(message
)
56 except (websockets
.exceptions
.WebSocketException
, RuntimeError, TimeoutError
) as e
:
57 raise TransportError(cause
=e
) from e
58 except SocksProxyError
as e
:
59 raise ProxyError(cause
=e
) from e
60 except TypeError as e
:
61 raise RequestError(cause
=e
) from e
64 # https://websockets.readthedocs.io/en/stable/reference/sync/client.html#websockets.sync.client.ClientConnection.recv
66 return self
.wsw
.recv()
67 except SocksProxyError
as e
:
68 raise ProxyError(cause
=e
) from e
69 except (websockets
.exceptions
.WebSocketException
, RuntimeError, TimeoutError
) as e
:
70 raise TransportError(cause
=e
) from e
74 class WebsocketsRH(WebSocketRequestHandler
):
76 Websockets request handler
77 https://websockets.readthedocs.io
78 https://github.com/python-websockets/websockets
80 _SUPPORTED_URL_SCHEMES
= ('wss', 'ws')
81 _SUPPORTED_PROXY_SCHEMES
= ('socks4', 'socks4a', 'socks5', 'socks5h')
82 _SUPPORTED_FEATURES
= (Features
.ALL_PROXY
, Features
.NO_PROXY
)
83 RH_NAME
= 'websockets'
85 def __init__(self
, *args
, **kwargs
):
86 super().__init
__(*args
, **kwargs
)
87 for name
in ('websockets.client', 'websockets.server'):
88 logger
= logging
.getLogger(name
)
89 handler
= logging
.StreamHandler(stream
=sys
.stdout
)
90 handler
.setFormatter(logging
.Formatter(f
'{self.RH_NAME}: %(message)s'))
91 logger
.addHandler(handler
)
93 logger
.setLevel(logging
.DEBUG
)
95 def _check_extensions(self
, extensions
):
96 super()._check
_extensions
(extensions
)
97 extensions
.pop('timeout', None)
98 extensions
.pop('cookiejar', None)
100 def _send(self
, request
):
101 timeout
= float(request
.extensions
.get('timeout') or self
.timeout
)
102 headers
= self
._merge
_headers
(request
.headers
)
103 if 'cookie' not in headers
:
104 cookiejar
= request
.extensions
.get('cookiejar') or self
.cookiejar
105 cookie_header
= cookiejar
.get_cookie_header(request
.url
)
107 headers
['cookie'] = cookie_header
109 wsuri
= parse_uri(request
.url
)
110 create_conn_kwargs
= {
111 'source_address': (self
.source_address
, 0) if self
.source_address
else None,
114 proxy
= select_proxy(request
.url
, request
.proxies
or self
.proxies
or {})
117 socks_proxy_options
= make_socks_proxy_opts(proxy
)
118 sock
= create_connection(
119 address
=(socks_proxy_options
['addr'], socks_proxy_options
['port']),
120 _create_socket_func
=functools
.partial(
121 create_socks_proxy_socket
, (wsuri
.host
, wsuri
.port
), socks_proxy_options
),
125 sock
= create_connection(
126 address
=(wsuri
.host
, wsuri
.port
),
129 conn
= websockets
.sync
.client
.connect(
132 additional_headers
=headers
,
133 open_timeout
=timeout
,
134 user_agent_header
=None,
135 ssl_context
=self
._make
_sslcontext
() if wsuri
.secure
else None,
136 close_timeout
=0, # not ideal, but prevents yt-dlp hanging
138 return WebsocketsResponseAdapter(conn
, url
=request
.url
)
140 # Exceptions as per https://websockets.readthedocs.io/en/stable/reference/sync/client.html
141 except SocksProxyError
as e
:
142 raise ProxyError(cause
=e
) from e
143 except websockets
.exceptions
.InvalidURI
as e
:
144 raise RequestError(cause
=e
) from e
145 except ssl
.SSLCertVerificationError
as e
:
146 raise CertificateVerifyError(cause
=e
) from e
147 except ssl
.SSLError
as e
:
148 raise SSLError(cause
=e
) from e
149 except websockets
.exceptions
.InvalidStatus
as e
:
152 fp
=io
.BytesIO(e
.response
.body
),
154 headers
=e
.response
.headers
,
155 status
=e
.response
.status_code
,
156 reason
=e
.response
.reason_phrase
),
158 except (OSError, TimeoutError
, websockets
.exceptions
.WebSocketException
) as e
:
159 raise TransportError(cause
=e
) from e