Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/websocket/_core.py: 19%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import socket
2import struct
3import threading
4import time
5from typing import Any, Callable, Optional, Type, Union
6from urllib.parse import urlparse
8# websocket modules
9from ._abnf import ABNF, STATUS_NORMAL, continuous_frame, frame_buffer
10from ._exceptions import (
11 WebSocketProtocolException,
12 WebSocketConnectionClosedException,
13 WebSocketTimeoutException,
14 WebSocketException,
15)
16from ._handshake import SUPPORTED_REDIRECT_STATUSES, handshake, handshake_response
17from ._http import connect, proxy_info
18from ._logging import debug, error, trace, isEnabledForError, isEnabledForTrace
19from ._socket import _validate_timeout, getdefaulttimeout, recv, send, sock_opt
20from ._ssl_compat import ssl
21from ._utils import NoLock
22from ._dispatcher import DispatcherBase, WrappedDispatcher
24"""
25_core.py
26websocket - WebSocket client library for Python
28Copyright 2026 engn33r
30Licensed under the Apache License, Version 2.0 (the "License");
31you may not use this file except in compliance with the License.
32You may obtain a copy of the License at
34 http://www.apache.org/licenses/LICENSE-2.0
36Unless required by applicable law or agreed to in writing, software
37distributed under the License is distributed on an "AS IS" BASIS,
38WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39See the License for the specific language governing permissions and
40limitations under the License.
41"""
43__all__ = ["WebSocket", "create_connection"]
46def _normalize_close_reason(reason: Union[str, bytes, None]) -> bytes:
47 """Convert a close reason into the UTF-8 bytes for a close-frame payload."""
48 if reason is None:
49 return b""
50 if isinstance(reason, str):
51 return reason.encode("utf-8")
52 if isinstance(reason, bytes):
53 return reason
54 return bytes(reason)
57def _handshake_authority(url: str, addrs: tuple) -> tuple:
58 """(scheme, lowercase host, effective port) identifying a handshake target."""
59 scheme = url.split(":", 1)[0].lower()
60 hostname, port, _ = addrs
61 return (scheme, hostname.lower() if hostname else None, port)
64def _url_origin(url: str) -> str:
65 """scheme://host[:port] of url, for error messages. The userinfo, path and
66 query can carry credentials or tokens and must not end up in logs."""
67 parsed = urlparse(url)
68 host = parsed.hostname or ""
69 if ":" in host:
70 host = f"[{host}]"
71 try:
72 port = parsed.port
73 except ValueError: # a malformed port in a server-supplied Location
74 port = None
75 return f"{parsed.scheme}://{host}" + (f":{port}" if port is not None else "")
78# Headers tied to the authority the caller targeted. They must not follow a
79# redirect to a different scheme/host/port.
80_AUTHORITY_SCOPED_HEADERS = ("authorization", "proxy-authorization", "cookie", "host")
83def _without_credentials(options: dict) -> dict:
84 """Copy of options without the Authorization, Proxy-Authorization, Cookie
85 and Host headers and the cookie and host options, for a redirect whose
86 target authority differs from the previous one."""
87 options = dict(options)
88 header = options.get("header")
89 if header:
90 if isinstance(header, dict):
91 header = {
92 k: v
93 for k, v in header.items()
94 if k.strip().lower() not in _AUTHORITY_SCOPED_HEADERS
95 }
96 else:
97 header = [
98 h
99 for h in header
100 if h.split(":", 1)[0].strip().lower() not in _AUTHORITY_SCOPED_HEADERS
101 ]
102 options["header"] = header
103 options.pop("cookie", None)
104 options.pop("host", None)
105 return options
108class WebSocket:
109 """
110 Low level WebSocket interface.
112 This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_
114 We can connect to the websocket server and send/receive data.
115 The following example is an echo client.
117 >>> import websocket
118 >>> ws = websocket.WebSocket()
119 >>> ws.connect("ws://websockets.chilkat.io/wsChilkatEcho.ashx")
120 >>> ws.send("Hello, Server")
121 19
122 >>> ws.recv()
123 'Hello, Server'
124 >>> ws.close()
126 Parameters
127 ----------
128 get_mask_key: func
129 A callable function to get new mask keys, see the
130 WebSocket.set_mask_key's docstring for more information.
131 sockopt: tuple
132 Values for socket.setsockopt.
133 sockopt must be tuple and each element is argument of sock.setsockopt.
134 sslopt: dict
135 Optional dict object for ssl socket options. See FAQ for details.
136 fire_cont_frame: bool
137 Fire recv event for each cont frame. Default is False.
138 enable_multithread: bool
139 If set to True, lock send method.
140 skip_utf8_validation: bool
141 Skip utf8 validation.
142 """
144 def __init__(
145 self,
146 get_mask_key: Optional[Callable] = None,
147 sockopt: Optional[list] = None,
148 sslopt: Optional[dict] = None,
149 fire_cont_frame: bool = False,
150 enable_multithread: bool = True,
151 skip_utf8_validation: bool = False,
152 dispatcher: Optional[Union[DispatcherBase, WrappedDispatcher]] = None,
153 **_: Any,
154 ) -> None:
155 """
156 Initialize WebSocket object.
158 Parameters
159 ----------
160 sslopt: dict
161 Optional dict object for ssl socket options. See FAQ for details.
162 """
163 self.sock_opt = sock_opt(sockopt, sslopt)
164 self.handshake_response: Optional[handshake_response] = None
165 self.sock: Optional[socket.socket] = None
167 self.connected = False
168 self.close_frame: Optional[ABNF] = None
169 # True while close() waits for the peer's close frame, so a concurrent
170 # close() does not tear the socket down underneath it.
171 self._closing = False
172 self.get_mask_key = get_mask_key
173 # These buffer over the build-up of a single frame.
174 self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
175 self.cont_frame = continuous_frame(fire_cont_frame, skip_utf8_validation)
176 self.dispatcher = dispatcher
178 if enable_multithread:
179 self.lock = threading.Lock()
180 self.readlock = threading.Lock()
181 else:
182 self.lock = NoLock() # type: ignore[assignment]
183 self.readlock = NoLock() # type: ignore[assignment]
185 def __iter__(self):
186 """
187 Allow iteration over websocket, implying sequential `recv` executions.
188 """
189 while True:
190 yield self.recv()
192 def __next__(self):
193 return self.recv()
195 def next(self):
196 return self.__next__()
198 def fileno(self):
199 if self.sock is None:
200 raise WebSocketException("Connection not established")
201 return self.sock.fileno()
203 def set_mask_key(self, func):
204 """
205 Set function to create mask key. You can customize mask key generator.
206 Mainly, this is for testing purpose.
208 Parameters
209 ----------
210 func: func
211 callable object. the func takes 1 argument as integer.
212 The argument means length of mask key.
213 This func must return string(byte array),
214 which length is argument specified.
215 """
216 self.get_mask_key = func
218 def gettimeout(self) -> Optional[Union[float, int]]:
219 """
220 Get the websocket timeout (in seconds) as an int or float
222 Returns
223 ----------
224 timeout: int or float
225 returns timeout value (in seconds). This value could be either float/integer.
226 """
227 return self.sock_opt.timeout
229 def settimeout(self, timeout: Optional[Union[float, int]]) -> None:
230 """
231 Set the timeout to the websocket.
233 Parameters
234 ----------
235 timeout: int or float
236 timeout time (in seconds). This value could be either float/integer.
237 None disables the timeout (blocking mode).
238 """
239 _validate_timeout(timeout)
240 self.sock_opt.timeout = timeout
241 if self.sock:
242 self.sock.settimeout(timeout)
244 timeout = property(gettimeout, settimeout)
246 def getsubprotocol(self) -> Optional[str]:
247 """
248 Get subprotocol
249 """
250 if self.handshake_response:
251 return self.handshake_response.subprotocol
252 else:
253 return None
255 subprotocol = property(getsubprotocol)
257 def getstatus(self) -> Optional[int]:
258 """
259 Get handshake status
260 """
261 if self.handshake_response:
262 return self.handshake_response.status
263 else:
264 return None
266 status = property(getstatus)
268 def getheaders(self) -> Optional[dict]:
269 """
270 Get handshake response header
271 """
272 if self.handshake_response:
273 return self.handshake_response.headers
274 else:
275 return None
277 def is_ssl(self):
278 try:
279 return isinstance(self.sock, ssl.SSLSocket)
280 except (AttributeError, NameError):
281 return False
283 headers = property(getheaders)
285 def connect(self, url, **options):
286 """
287 Connect to url. url is websocket url scheme.
288 ie. ws://host:port/resource
289 You can customize using 'options'.
290 If you set "header" list object, you can set your own custom header.
292 >>> ws = WebSocket()
293 >>> ws.connect("ws://websockets.chilkat.io/wsChilkatEcho.ashx",
294 ... header=["User-Agent: MyProgram",
295 ... "x-custom: header"])
297 Parameters
298 ----------
299 header: list or dict
300 Custom http header list or dict.
301 cookie: str
302 Cookie value.
303 origin: str
304 Custom origin url.
305 connection: str
306 Custom connection header value.
307 Default value "Upgrade" set in _handshake.py
308 suppress_origin: bool
309 Suppress outputting origin header.
310 suppress_host: bool
311 Suppress outputting host header.
312 host: str
313 Custom host header string.
314 timeout: int or float
315 Socket timeout time. This value is an integer or float.
316 If you set None for this value, it means "use default_timeout value"
317 http_proxy_host: str
318 HTTP proxy host name.
319 http_proxy_port: str or int
320 HTTP proxy port. Required when http_proxy_host is set. Proxies
321 from environment variables default to port 80.
322 http_no_proxy: list
323 Whitelisted host names that don't use the proxy.
324 http_proxy_auth: tuple
325 HTTP proxy auth information. Tuple of username and password. Default is None.
326 http_proxy_timeout: int or float
327 HTTP proxy timeout, default is 60 sec as per python-socks.
328 redirect_limit: int
329 Number of redirects to follow. A redirect from wss:// to ws://
330 is refused with WebSocketException.
331 subprotocols: list
332 List of available subprotocols. Default is None.
333 socket: socket
334 Pre-initialized stream socket. With a wss:// URL, a socket that is
335 not already an ssl.SSLSocket is wrapped in TLS using sslopt.
336 """
337 if "timeout" in options:
338 _validate_timeout(options["timeout"])
339 self.sock_opt.timeout = options.get("timeout", self.sock_opt.timeout)
340 self.sock, addrs = connect(
341 url, self.sock_opt, proxy_info(**options), options.pop("socket", None)
342 )
344 try:
345 self.handshake_response = handshake(self.sock, url, *addrs, **options)
346 authority = _handshake_authority(url, addrs)
347 for _ in range(options.pop("redirect_limit", 3)):
348 if (
349 self.handshake_response is not None
350 and self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES
351 ):
352 location = self.handshake_response.headers.get("location")
353 if location is None:
354 raise WebSocketException(
355 "Redirect response without Location header, "
356 f"status {self.handshake_response.status}"
357 )
358 if (
359 urlparse(url).scheme.lower() == "wss"
360 and urlparse(location).scheme.lower() == "ws"
361 ):
362 raise WebSocketException(
363 f"Refusing redirect from {_url_origin(url)} to "
364 f"{_url_origin(location)}: "
365 "it would downgrade wss:// to unencrypted ws://"
366 )
367 url = location
368 self.sock.close()
369 try:
370 self.sock, addrs = connect(
371 url,
372 self.sock_opt,
373 proxy_info(**options),
374 options.pop("socket", None),
375 )
376 except ValueError as e:
377 raise WebSocketException(
378 f"Invalid redirect target {url!r}: {e}"
379 ) from e
380 new_authority = _handshake_authority(url, addrs)
381 if new_authority != authority:
382 # Credentials supplied for the original authority must
383 # not be replayed to a different scheme/host/port.
384 options = _without_credentials(options)
385 authority = new_authority
386 self.handshake_response = handshake(
387 self.sock, url, *addrs, **options
388 )
389 if (
390 self.handshake_response is not None
391 and self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES
392 ):
393 raise WebSocketException("Redirect limit exhausted")
394 self.connected = True
395 except:
396 if self.sock:
397 self.sock.close()
398 self.sock = None
399 raise
401 def send(self, payload: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> int:
402 """
403 Send the data as string.
405 Parameters
406 ----------
407 payload: str
408 Payload must be utf-8 string or unicode,
409 If the opcode is OPCODE_TEXT.
410 Otherwise, it must be string(byte array).
411 opcode: int
412 Operation code (opcode) to send.
413 """
415 frame = ABNF.create_frame(payload, opcode)
416 return self.send_frame(frame)
418 def send_text(self, text_data: str) -> int:
419 """
420 Sends UTF-8 encoded text.
421 """
422 return self.send(text_data, ABNF.OPCODE_TEXT)
424 def send_bytes(self, data: Union[bytes, bytearray]) -> int:
425 """
426 Sends a sequence of bytes.
427 """
428 return self.send(data, ABNF.OPCODE_BINARY)
430 def send_frame(self, frame: ABNF) -> int:
431 """
432 Send the data frame.
434 >>> ws = create_connection("ws://websockets.chilkat.io/wsChilkatEcho.ashx")
435 >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
436 >>> ws.send_frame(frame)
437 >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
438 >>> ws.send_frame(frame)
439 >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
440 >>> ws.send_frame(frame)
442 Parameters
443 ----------
444 frame: ABNF frame
445 frame data created by ABNF.create_frame
446 """
447 if self.get_mask_key:
448 frame.get_mask_key = self.get_mask_key
449 data = frame.format()
450 length = len(data)
451 if isEnabledForTrace():
452 trace(f"++Sent raw: {repr(data)}")
453 trace(f"++Sent decoded: {frame.__str__()}")
454 with self.lock:
455 while data:
456 bytes_sent = self._send(data)
457 data = data[bytes_sent:]
459 return length
461 def send_binary(self, payload: bytes) -> int:
462 """
463 Send a binary message (OPCODE_BINARY).
465 Parameters
466 ----------
467 payload: bytes
468 payload of message to send.
469 """
470 return self.send(payload, ABNF.OPCODE_BINARY)
472 def ping(self, payload: Union[str, bytes] = "") -> None:
473 """
474 Send ping data.
476 Parameters
477 ----------
478 payload: str
479 data payload to send server.
480 """
481 if isinstance(payload, str):
482 payload = payload.encode("utf-8")
483 self.send(payload, ABNF.OPCODE_PING)
485 def pong(self, payload: Union[str, bytes] = "") -> None:
486 """
487 Send pong data.
489 Parameters
490 ----------
491 payload: str
492 data payload to send server.
493 """
494 if isinstance(payload, str):
495 payload = payload.encode("utf-8")
496 self.send(payload, ABNF.OPCODE_PONG)
498 def recv(self) -> Union[str, bytes]:
499 """
500 Receive string data(byte array) from the server.
502 Returns
503 ----------
504 data: string (byte array) value.
505 """
506 with self.readlock:
507 opcode, data = self.recv_data()
508 if opcode == ABNF.OPCODE_TEXT:
509 data_received: Union[bytes, str] = data
510 if isinstance(data_received, bytes):
511 return data_received.decode("utf-8")
512 elif isinstance(data_received, str):
513 return data_received
514 elif opcode == ABNF.OPCODE_BINARY:
515 data_binary: bytes = data
516 return data_binary
517 else:
518 return ""
520 def recv_data(self, control_frame: bool = False) -> tuple:
521 """
522 Receive data with operation code.
524 Parameters
525 ----------
526 control_frame: bool
527 a boolean flag indicating whether to return control frame
528 data, defaults to False
530 Returns
531 -------
532 opcode, frame.data: tuple
533 tuple of operation code and string(byte array) value.
534 """
535 opcode, frame = self.recv_data_frame(control_frame)
536 return opcode, frame.data
538 def recv_data_frame(self, control_frame: bool = False) -> tuple:
539 """
540 Receive data with operation code.
542 If a valid ping message is received, a pong response is sent.
544 Parameters
545 ----------
546 control_frame: bool
547 a boolean flag indicating whether to return control frame
548 data, defaults to False
550 Returns
551 -------
552 frame.opcode, frame: tuple
553 tuple of operation code and string(byte array) value.
554 """
555 while True:
556 frame = self.recv_frame()
557 if isEnabledForTrace():
558 trace(f"++Rcv raw: {repr(frame.format())}")
559 trace(f"++Rcv decoded: {frame.__str__()}")
560 if frame.opcode in (
561 ABNF.OPCODE_TEXT,
562 ABNF.OPCODE_BINARY,
563 ABNF.OPCODE_CONT,
564 ):
565 self.cont_frame.validate(frame)
566 self.cont_frame.add(frame)
568 if self.cont_frame.is_fire(frame):
569 return self.cont_frame.extract(frame)
571 elif frame.opcode == ABNF.OPCODE_CLOSE:
572 self.send_close()
573 return frame.opcode, frame
574 elif frame.opcode == ABNF.OPCODE_PING:
575 if len(frame.data) < 126:
576 self.pong(frame.data)
577 else:
578 raise WebSocketProtocolException("Ping message is too long")
579 if control_frame:
580 return frame.opcode, frame
581 elif frame.opcode == ABNF.OPCODE_PONG:
582 if control_frame:
583 return frame.opcode, frame
585 def recv_frame(self):
586 """
587 Receive data as frame from server.
589 Returns
590 -------
591 self.frame_buffer.recv_frame(): ABNF frame object
592 """
593 return self.frame_buffer.recv_frame()
595 def send_close(
596 self, status: int = STATUS_NORMAL, reason: Union[str, bytes] = b""
597 ) -> None:
598 """
599 Send close data to the server.
601 Parameters
602 ----------
603 status: int
604 Status code to send. See STATUS_XXX.
605 reason: str or bytes
606 The reason to close. This must be string or UTF-8 bytes.
607 """
608 if status < 0 or status >= ABNF.LENGTH_16:
609 raise ValueError("code is invalid range")
611 reason_bytes = _normalize_close_reason(reason)
613 self.connected = False
614 self.send(struct.pack("!H", status) + reason_bytes, ABNF.OPCODE_CLOSE)
616 def close(
617 self,
618 status: int = STATUS_NORMAL,
619 reason: Union[str, bytes] = b"",
620 timeout: Optional[Union[int, float]] = 3,
621 ) -> None:
622 """
623 Close Websocket object
625 Parameters
626 ----------
627 status: int
628 Status code to send. See VALID_CLOSE_STATUS in ABNF.
629 reason: str or bytes
630 The reason to close in UTF-8.
631 timeout: int or float
632 Timeout until receive a close frame.
633 If None, it will wait forever until receive a close frame.
634 """
635 if not self.connected:
636 # The peer already disconnected: free the socket (#1066). Leave it
637 # alone if another thread is mid-close; that thread shuts it down.
638 if not self._closing:
639 self.shutdown()
640 return
641 if status < 0 or status >= ABNF.LENGTH_16:
642 raise ValueError("code is invalid range")
644 # Reset close_frame to avoid stale data from previous connections
645 self.close_frame = None
647 self._closing = True
648 try:
649 self.connected = False
650 self.send(
651 struct.pack("!H", status) + _normalize_close_reason(reason),
652 ABNF.OPCODE_CLOSE,
653 )
654 if self.sock is None:
655 return
656 sock_timeout = self.sock.gettimeout()
657 self.sock.settimeout(timeout)
658 start_time = time.time()
659 while timeout is None or time.time() - start_time < timeout:
660 try:
661 frame = self.recv_frame()
662 if frame.opcode != ABNF.OPCODE_CLOSE:
663 continue
664 # Store the peer's close frame for access by higher-level APIs
665 self.close_frame = frame
666 if isEnabledForError():
667 recv_status = struct.unpack("!H", frame.data[0:2])[0]
668 if recv_status >= 3000 and recv_status <= 4999:
669 debug(f"close status: {repr(recv_status)}")
670 elif recv_status != STATUS_NORMAL:
671 error(f"close status: {repr(recv_status)}")
672 break
673 except (
674 WebSocketConnectionClosedException,
675 WebSocketTimeoutException,
676 struct.error,
677 ):
678 break
679 if self.sock is not None:
680 self.sock.settimeout(sock_timeout)
681 self.sock.shutdown(socket.SHUT_RDWR)
682 except:
683 pass
684 finally:
685 self._closing = False
687 self.shutdown()
689 def abort(self):
690 """
691 Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
692 """
693 if self.connected and self.sock is not None:
694 try:
695 self.sock.shutdown(socket.SHUT_RDWR)
696 except (OSError, AttributeError):
697 # Socket already closed or never connected
698 # abort() is best-effort, like shutdown()
699 debug("Socket already closed during abort")
701 def shutdown(self):
702 """
703 close socket, immediately.
704 """
705 if self.sock:
706 try:
707 # Check if socket is still open before closing
708 if not self.sock._closed:
709 self.sock.close()
710 except (OSError, AttributeError):
711 # Socket already closed or invalid file descriptor - this can happen
712 # during reconnection scenarios when network failures occur
713 debug("Socket already closed during shutdown")
714 pass
715 finally:
716 self.sock = None
717 self.connected = False
719 def _send(self, data: Union[str, bytes]) -> int:
720 if self.sock is None:
721 raise WebSocketConnectionClosedException("socket is already closed.")
722 if self.dispatcher:
723 return self.dispatcher.send(self.sock, data)
724 return send(self.sock, data)
726 def _recv(self, bufsize):
727 if self.sock is None:
728 raise WebSocketConnectionClosedException("Connection is closed")
729 try:
730 return recv(self.sock, bufsize)
731 except WebSocketConnectionClosedException:
732 if self.sock:
733 self.sock.close()
734 self.sock = None
735 self.connected = False
736 raise
739def create_connection(
740 url: str,
741 timeout: Optional[Union[float, int]] = None,
742 class_: Type[WebSocket] = WebSocket,
743 **options: Any,
744) -> WebSocket:
745 """
746 Connect to url and return websocket object.
748 Connect to url and return the WebSocket object.
749 Passing optional timeout parameter will set the timeout on the socket.
750 If no timeout is supplied,
751 the global default timeout setting returned by getdefaulttimeout() is used.
752 You can customize using 'options'.
753 If you set "header" list object, you can set your own custom header.
755 >>> conn = create_connection("ws://websockets.chilkat.io/wsChilkatEcho.ashx",
756 ... header=["User-Agent: MyProgram",
757 ... "x-custom: header"])
759 Parameters
760 ----------
761 class_: class
762 class to instantiate when creating the connection. It has to implement
763 settimeout and connect. It's __init__ should be compatible with
764 WebSocket.__init__, i.e. accept all of it's kwargs.
765 header: list or dict
766 custom http header list or dict.
767 cookie: str
768 Cookie value.
769 origin: str
770 custom origin url.
771 suppress_origin: bool
772 suppress outputting origin header.
773 host: str
774 custom host header string.
775 timeout: int or float
776 socket timeout time. This value could be either float/integer.
777 If set to None, it uses the default_timeout value.
778 http_proxy_host: str
779 HTTP proxy host name.
780 http_proxy_port: str or int
781 HTTP proxy port. Required when http_proxy_host is set. Proxies
782 from environment variables default to port 80.
783 http_no_proxy: list
784 Whitelisted host names that don't use the proxy.
785 http_proxy_auth: tuple
786 HTTP proxy auth information. tuple of username and password. Default is None.
787 http_proxy_timeout: int or float
788 HTTP proxy timeout, default is 60 sec as per python-socks.
789 enable_multithread: bool
790 Enable lock for multithread.
791 redirect_limit: int
792 Number of redirects to follow. A redirect from wss:// to ws://
793 is refused with WebSocketException.
794 sockopt: tuple
795 Values for socket.setsockopt.
796 sockopt must be a tuple and each element is an argument of sock.setsockopt.
797 sslopt: dict
798 Optional dict object for ssl socket options. See FAQ for details.
799 subprotocols: list
800 List of available subprotocols. Default is None.
801 skip_utf8_validation: bool
802 Skip utf8 validation.
803 socket: socket
804 Pre-initialized stream socket. With a wss:// URL, a socket that is
805 not already an ssl.SSLSocket is wrapped in TLS using sslopt.
806 """
807 sockopt = options.pop("sockopt", [])
808 sslopt = options.pop("sslopt", {})
809 fire_cont_frame = options.pop("fire_cont_frame", False)
810 enable_multithread = options.pop("enable_multithread", True)
811 skip_utf8_validation = options.pop("skip_utf8_validation", False)
812 websock = class_(
813 sockopt=sockopt,
814 sslopt=sslopt,
815 fire_cont_frame=fire_cont_frame,
816 enable_multithread=enable_multithread,
817 skip_utf8_validation=skip_utf8_validation,
818 **options,
819 )
820 websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
821 websock.connect(url, **options)
822 return websock