mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 21:21:15 -07:00
Bump websocket-client from 1.5.1 to 1.6.2 (#2122)
* Bump websocket-client from 1.5.1 to 1.6.2 Bumps [websocket-client](https://github.com/websocket-client/websocket-client) from 1.5.1 to 1.6.2. - [Release notes](https://github.com/websocket-client/websocket-client/releases) - [Changelog](https://github.com/websocket-client/websocket-client/blob/master/ChangeLog) - [Commits](https://github.com/websocket-client/websocket-client/compare/v1.5.1...v1.6.2) --- updated-dependencies: - dependency-name: websocket-client dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update websocket-client==1.6.2 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
c93f470371
commit
eac78a3047
22 changed files with 295 additions and 260 deletions
|
@ -2,7 +2,7 @@
|
||||||
__init__.py
|
__init__.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -23,4 +23,4 @@ from ._exceptions import *
|
||||||
from ._logging import *
|
from ._logging import *
|
||||||
from ._socket import *
|
from ._socket import *
|
||||||
|
|
||||||
__version__ = "1.5.1"
|
__version__ = "1.6.2"
|
||||||
|
|
|
@ -11,7 +11,7 @@ from threading import Lock
|
||||||
_abnf.py
|
_abnf.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -33,14 +33,14 @@ try:
|
||||||
# Note that wsaccel is unmaintained.
|
# Note that wsaccel is unmaintained.
|
||||||
from wsaccel.xormask import XorMaskerSimple
|
from wsaccel.xormask import XorMaskerSimple
|
||||||
|
|
||||||
def _mask(_m, _d):
|
def _mask(_m, _d) -> bytes:
|
||||||
return XorMaskerSimple(_m).process(_d)
|
return XorMaskerSimple(_m).process(_d)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# wsaccel is not available, use websocket-client _mask()
|
# wsaccel is not available, use websocket-client _mask()
|
||||||
native_byteorder = sys.byteorder
|
native_byteorder = sys.byteorder
|
||||||
|
|
||||||
def _mask(mask_value, data_value):
|
def _mask(mask_value: array.array, data_value: array.array) -> bytes:
|
||||||
datalen = len(data_value)
|
datalen = len(data_value)
|
||||||
data_value = int.from_bytes(data_value, native_byteorder)
|
data_value = int.from_bytes(data_value, native_byteorder)
|
||||||
mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder)
|
mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder)
|
||||||
|
@ -131,8 +131,8 @@ class ABNF:
|
||||||
LENGTH_16 = 1 << 16
|
LENGTH_16 = 1 << 16
|
||||||
LENGTH_63 = 1 << 63
|
LENGTH_63 = 1 << 63
|
||||||
|
|
||||||
def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
|
def __init__(self, fin: int = 0, rsv1: int = 0, rsv2: int = 0, rsv3: int = 0,
|
||||||
opcode=OPCODE_TEXT, mask=1, data=""):
|
opcode: int = OPCODE_TEXT, mask: int = 1, data: str or bytes = "") -> None:
|
||||||
"""
|
"""
|
||||||
Constructor for ABNF. Please check RFC for arguments.
|
Constructor for ABNF. Please check RFC for arguments.
|
||||||
"""
|
"""
|
||||||
|
@ -147,7 +147,7 @@ class ABNF:
|
||||||
self.data = data
|
self.data = data
|
||||||
self.get_mask_key = os.urandom
|
self.get_mask_key = os.urandom
|
||||||
|
|
||||||
def validate(self, skip_utf8_validation=False) -> None:
|
def validate(self, skip_utf8_validation: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Validate the ABNF frame.
|
Validate the ABNF frame.
|
||||||
|
|
||||||
|
@ -187,19 +187,19 @@ class ABNF:
|
||||||
+ " data=" + str(self.data)
|
+ " data=" + str(self.data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_frame(data, opcode, fin=1):
|
def create_frame(data: str, opcode: int, fin: int = 1) -> 'ABNF':
|
||||||
"""
|
"""
|
||||||
Create frame to send text, binary and other data.
|
Create frame to send text, binary and other data.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
data: <type>
|
data: str
|
||||||
data to send. This is string value(byte array).
|
data to send. This is string value(byte array).
|
||||||
If opcode is OPCODE_TEXT and this value is unicode,
|
If opcode is OPCODE_TEXT and this value is unicode,
|
||||||
data value is converted into unicode string, automatically.
|
data value is converted into unicode string, automatically.
|
||||||
opcode: <type>
|
opcode: int
|
||||||
operation code. please see OPCODE_XXX.
|
operation code. please see OPCODE_MAP.
|
||||||
fin: <type>
|
fin: int
|
||||||
fin flag. if set to 0, create continue fragmentation.
|
fin flag. if set to 0, create continue fragmentation.
|
||||||
"""
|
"""
|
||||||
if opcode == ABNF.OPCODE_TEXT and isinstance(data, str):
|
if opcode == ABNF.OPCODE_TEXT and isinstance(data, str):
|
||||||
|
@ -237,7 +237,7 @@ class ABNF:
|
||||||
mask_key = self.get_mask_key(4)
|
mask_key = self.get_mask_key(4)
|
||||||
return frame_header + self._get_masked(mask_key)
|
return frame_header + self._get_masked(mask_key)
|
||||||
|
|
||||||
def _get_masked(self, mask_key):
|
def _get_masked(self, mask_key: str or bytes) -> bytes:
|
||||||
s = ABNF.mask(mask_key, self.data)
|
s = ABNF.mask(mask_key, self.data)
|
||||||
|
|
||||||
if isinstance(mask_key, str):
|
if isinstance(mask_key, str):
|
||||||
|
@ -246,7 +246,7 @@ class ABNF:
|
||||||
return mask_key + s
|
return mask_key + s
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mask(mask_key, data):
|
def mask(mask_key: str or bytes, data: str or bytes) -> bytes:
|
||||||
"""
|
"""
|
||||||
Mask or unmask data. Just do xor for each byte
|
Mask or unmask data. Just do xor for each byte
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ class frame_buffer:
|
||||||
_HEADER_MASK_INDEX = 5
|
_HEADER_MASK_INDEX = 5
|
||||||
_HEADER_LENGTH_INDEX = 6
|
_HEADER_LENGTH_INDEX = 6
|
||||||
|
|
||||||
def __init__(self, recv_fn, skip_utf8_validation):
|
def __init__(self, recv_fn: int, skip_utf8_validation: bool) -> None:
|
||||||
self.recv = recv_fn
|
self.recv = recv_fn
|
||||||
self.skip_utf8_validation = skip_utf8_validation
|
self.skip_utf8_validation = skip_utf8_validation
|
||||||
# Buffers over the packets from the layer beneath until desired amount
|
# Buffers over the packets from the layer beneath until desired amount
|
||||||
|
@ -282,7 +282,7 @@ class frame_buffer:
|
||||||
self.clear()
|
self.clear()
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
self.header = None
|
self.header = None
|
||||||
self.length = None
|
self.length = None
|
||||||
self.mask = None
|
self.mask = None
|
||||||
|
@ -290,7 +290,7 @@ class frame_buffer:
|
||||||
def has_received_header(self) -> bool:
|
def has_received_header(self) -> bool:
|
||||||
return self.header is None
|
return self.header is None
|
||||||
|
|
||||||
def recv_header(self):
|
def recv_header(self) -> None:
|
||||||
header = self.recv_strict(2)
|
header = self.recv_strict(2)
|
||||||
b1 = header[0]
|
b1 = header[0]
|
||||||
fin = b1 >> 7 & 1
|
fin = b1 >> 7 & 1
|
||||||
|
@ -304,7 +304,7 @@ class frame_buffer:
|
||||||
|
|
||||||
self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)
|
self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)
|
||||||
|
|
||||||
def has_mask(self):
|
def has_mask(self) -> bool or int:
|
||||||
if not self.header:
|
if not self.header:
|
||||||
return False
|
return False
|
||||||
return self.header[frame_buffer._HEADER_MASK_INDEX]
|
return self.header[frame_buffer._HEADER_MASK_INDEX]
|
||||||
|
@ -312,7 +312,7 @@ class frame_buffer:
|
||||||
def has_received_length(self) -> bool:
|
def has_received_length(self) -> bool:
|
||||||
return self.length is None
|
return self.length is None
|
||||||
|
|
||||||
def recv_length(self):
|
def recv_length(self) -> None:
|
||||||
bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
|
bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
|
||||||
length_bits = bits & 0x7f
|
length_bits = bits & 0x7f
|
||||||
if length_bits == 0x7e:
|
if length_bits == 0x7e:
|
||||||
|
@ -327,10 +327,10 @@ class frame_buffer:
|
||||||
def has_received_mask(self) -> bool:
|
def has_received_mask(self) -> bool:
|
||||||
return self.mask is None
|
return self.mask is None
|
||||||
|
|
||||||
def recv_mask(self):
|
def recv_mask(self) -> None:
|
||||||
self.mask = self.recv_strict(4) if self.has_mask() else ""
|
self.mask = self.recv_strict(4) if self.has_mask() else ""
|
||||||
|
|
||||||
def recv_frame(self):
|
def recv_frame(self) -> ABNF:
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Header
|
# Header
|
||||||
|
@ -386,20 +386,20 @@ class frame_buffer:
|
||||||
|
|
||||||
class continuous_frame:
|
class continuous_frame:
|
||||||
|
|
||||||
def __init__(self, fire_cont_frame, skip_utf8_validation):
|
def __init__(self, fire_cont_frame: bool, skip_utf8_validation: bool) -> None:
|
||||||
self.fire_cont_frame = fire_cont_frame
|
self.fire_cont_frame = fire_cont_frame
|
||||||
self.skip_utf8_validation = skip_utf8_validation
|
self.skip_utf8_validation = skip_utf8_validation
|
||||||
self.cont_data = None
|
self.cont_data = None
|
||||||
self.recving_frames = None
|
self.recving_frames = None
|
||||||
|
|
||||||
def validate(self, frame):
|
def validate(self, frame: ABNF) -> None:
|
||||||
if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
|
if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
|
||||||
raise WebSocketProtocolException("Illegal frame")
|
raise WebSocketProtocolException("Illegal frame")
|
||||||
if self.recving_frames and \
|
if self.recving_frames and \
|
||||||
frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
|
frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
|
||||||
raise WebSocketProtocolException("Illegal frame")
|
raise WebSocketProtocolException("Illegal frame")
|
||||||
|
|
||||||
def add(self, frame):
|
def add(self, frame: ABNF) -> None:
|
||||||
if self.cont_data:
|
if self.cont_data:
|
||||||
self.cont_data[1] += frame.data
|
self.cont_data[1] += frame.data
|
||||||
else:
|
else:
|
||||||
|
@ -410,10 +410,10 @@ class continuous_frame:
|
||||||
if frame.fin:
|
if frame.fin:
|
||||||
self.recving_frames = None
|
self.recving_frames = None
|
||||||
|
|
||||||
def is_fire(self, frame):
|
def is_fire(self, frame: ABNF) -> bool or int:
|
||||||
return frame.fin or self.fire_cont_frame
|
return frame.fin or self.fire_cont_frame
|
||||||
|
|
||||||
def extract(self, frame):
|
def extract(self, frame: ABNF) -> list:
|
||||||
data = self.cont_data
|
data = self.cont_data
|
||||||
self.cont_data = None
|
self.cont_data = None
|
||||||
frame.data = data[1]
|
frame.data = data[1]
|
||||||
|
|
|
@ -4,6 +4,9 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from typing import Callable, Any
|
||||||
|
|
||||||
from . import _logging
|
from . import _logging
|
||||||
from ._abnf import ABNF
|
from ._abnf import ABNF
|
||||||
|
@ -15,7 +18,7 @@ from ._exceptions import *
|
||||||
_app.py
|
_app.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -35,7 +38,7 @@ __all__ = ["WebSocketApp"]
|
||||||
RECONNECT = 0
|
RECONNECT = 0
|
||||||
|
|
||||||
|
|
||||||
def setReconnect(reconnectInterval):
|
def setReconnect(reconnectInterval: int) -> None:
|
||||||
global RECONNECT
|
global RECONNECT
|
||||||
RECONNECT = reconnectInterval
|
RECONNECT = reconnectInterval
|
||||||
|
|
||||||
|
@ -44,37 +47,40 @@ class DispatcherBase:
|
||||||
"""
|
"""
|
||||||
DispatcherBase
|
DispatcherBase
|
||||||
"""
|
"""
|
||||||
def __init__(self, app, ping_timeout):
|
def __init__(self, app: Any, ping_timeout: float) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
self.ping_timeout = ping_timeout
|
self.ping_timeout = ping_timeout
|
||||||
|
|
||||||
def timeout(self, seconds, callback):
|
def timeout(self, seconds: int, callback: Callable) -> None:
|
||||||
time.sleep(seconds)
|
time.sleep(seconds)
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
def reconnect(self, seconds, reconnector):
|
def reconnect(self, seconds: int, reconnector: Callable) -> None:
|
||||||
try:
|
try:
|
||||||
_logging.info("reconnect() - retrying in %s seconds [%s frames in stack]" % (seconds, len(inspect.stack())))
|
_logging.info("reconnect() - retrying in {seconds_count} seconds [{frame_count} frames in stack]".format(
|
||||||
|
seconds_count=seconds, frame_count=len(inspect.stack())))
|
||||||
time.sleep(seconds)
|
time.sleep(seconds)
|
||||||
reconnector(reconnecting=True)
|
reconnector(reconnecting=True)
|
||||||
except KeyboardInterrupt as e:
|
except KeyboardInterrupt as e:
|
||||||
_logging.info("User exited %s" % (e,))
|
_logging.info("User exited {err}".format(err=e))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(DispatcherBase):
|
class Dispatcher(DispatcherBase):
|
||||||
"""
|
"""
|
||||||
Dispatcher
|
Dispatcher
|
||||||
"""
|
"""
|
||||||
def read(self, sock, read_callback, check_callback):
|
def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
|
||||||
while self.app.keep_running:
|
|
||||||
sel = selectors.DefaultSelector()
|
sel = selectors.DefaultSelector()
|
||||||
sel.register(self.app.sock.sock, selectors.EVENT_READ)
|
sel.register(self.app.sock.sock, selectors.EVENT_READ)
|
||||||
|
try:
|
||||||
|
while self.app.keep_running:
|
||||||
r = sel.select(self.ping_timeout)
|
r = sel.select(self.ping_timeout)
|
||||||
if r:
|
if r:
|
||||||
if not read_callback():
|
if not read_callback():
|
||||||
break
|
break
|
||||||
check_callback()
|
check_callback()
|
||||||
|
finally:
|
||||||
sel.close()
|
sel.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,24 +88,26 @@ class SSLDispatcher(DispatcherBase):
|
||||||
"""
|
"""
|
||||||
SSLDispatcher
|
SSLDispatcher
|
||||||
"""
|
"""
|
||||||
def read(self, sock, read_callback, check_callback):
|
def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
|
||||||
|
sock = self.app.sock.sock
|
||||||
|
sel = selectors.DefaultSelector()
|
||||||
|
sel.register(sock, selectors.EVENT_READ)
|
||||||
|
try:
|
||||||
while self.app.keep_running:
|
while self.app.keep_running:
|
||||||
r = self.select()
|
r = self.select(sock, sel)
|
||||||
if r:
|
if r:
|
||||||
if not read_callback():
|
if not read_callback():
|
||||||
break
|
break
|
||||||
check_callback()
|
check_callback()
|
||||||
|
finally:
|
||||||
|
sel.close()
|
||||||
|
|
||||||
def select(self):
|
def select(self, sock, sel:selectors.DefaultSelector):
|
||||||
sock = self.app.sock.sock
|
sock = self.app.sock.sock
|
||||||
if sock.pending():
|
if sock.pending():
|
||||||
return [sock,]
|
return [sock,]
|
||||||
|
|
||||||
sel = selectors.DefaultSelector()
|
|
||||||
sel.register(sock, selectors.EVENT_READ)
|
|
||||||
|
|
||||||
r = sel.select(self.ping_timeout)
|
r = sel.select(self.ping_timeout)
|
||||||
sel.close()
|
|
||||||
|
|
||||||
if len(r) > 0:
|
if len(r) > 0:
|
||||||
return r[0][0]
|
return r[0][0]
|
||||||
|
@ -109,20 +117,20 @@ class WrappedDispatcher:
|
||||||
"""
|
"""
|
||||||
WrappedDispatcher
|
WrappedDispatcher
|
||||||
"""
|
"""
|
||||||
def __init__(self, app, ping_timeout, dispatcher):
|
def __init__(self, app, ping_timeout: float, dispatcher: Dispatcher) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
self.ping_timeout = ping_timeout
|
self.ping_timeout = ping_timeout
|
||||||
self.dispatcher = dispatcher
|
self.dispatcher = dispatcher
|
||||||
dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
|
dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
|
||||||
|
|
||||||
def read(self, sock, read_callback, check_callback):
|
def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
|
||||||
self.dispatcher.read(sock, read_callback)
|
self.dispatcher.read(sock, read_callback)
|
||||||
self.ping_timeout and self.timeout(self.ping_timeout, check_callback)
|
self.ping_timeout and self.timeout(self.ping_timeout, check_callback)
|
||||||
|
|
||||||
def timeout(self, seconds, callback):
|
def timeout(self, seconds: int, callback: Callable) -> None:
|
||||||
self.dispatcher.timeout(seconds, callback)
|
self.dispatcher.timeout(seconds, callback)
|
||||||
|
|
||||||
def reconnect(self, seconds, reconnector):
|
def reconnect(self, seconds: int, reconnector: Callable) -> None:
|
||||||
self.timeout(seconds, reconnector)
|
self.timeout(seconds, reconnector)
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,14 +139,14 @@ class WebSocketApp:
|
||||||
Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
|
Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, url, header=None,
|
def __init__(self, url: str, header: list or dict or Callable = None,
|
||||||
on_open=None, on_message=None, on_error=None,
|
on_open: Callable = None, on_message: Callable = None, on_error: Callable = None,
|
||||||
on_close=None, on_ping=None, on_pong=None,
|
on_close: Callable = None, on_ping: Callable = None, on_pong: Callable = None,
|
||||||
on_cont_message=None,
|
on_cont_message: Callable = None,
|
||||||
keep_running=True, get_mask_key=None, cookie=None,
|
keep_running: bool = True, get_mask_key: Callable = None, cookie: str = None,
|
||||||
subprotocols=None,
|
subprotocols: list = None,
|
||||||
on_data=None,
|
on_data: Callable = None,
|
||||||
socket=None):
|
socket: socket.socket = None) -> None:
|
||||||
"""
|
"""
|
||||||
WebSocketApp initialization
|
WebSocketApp initialization
|
||||||
|
|
||||||
|
@ -146,8 +154,11 @@ class WebSocketApp:
|
||||||
----------
|
----------
|
||||||
url: str
|
url: str
|
||||||
Websocket url.
|
Websocket url.
|
||||||
header: list or dict
|
header: list or dict or Callable
|
||||||
Custom header for websocket handshake.
|
Custom header for websocket handshake.
|
||||||
|
If the parameter is a callable object, it is called just before the connection attempt.
|
||||||
|
The returned dict or list is used as custom header value.
|
||||||
|
This could be useful in order to properly setup timestamp dependent headers.
|
||||||
on_open: function
|
on_open: function
|
||||||
Callback object which is called at opening websocket.
|
Callback object which is called at opening websocket.
|
||||||
on_open has one argument.
|
on_open has one argument.
|
||||||
|
@ -222,8 +233,10 @@ class WebSocketApp:
|
||||||
self.subprotocols = subprotocols
|
self.subprotocols = subprotocols
|
||||||
self.prepared_socket = socket
|
self.prepared_socket = socket
|
||||||
self.has_errored = False
|
self.has_errored = False
|
||||||
|
self.has_done_teardown = False
|
||||||
|
self.has_done_teardown_lock = threading.Lock()
|
||||||
|
|
||||||
def send(self, data, opcode=ABNF.OPCODE_TEXT):
|
def send(self, data: str, opcode: int = ABNF.OPCODE_TEXT) -> None:
|
||||||
"""
|
"""
|
||||||
send message
|
send message
|
||||||
|
|
||||||
|
@ -240,7 +253,7 @@ class WebSocketApp:
|
||||||
raise WebSocketConnectionClosedException(
|
raise WebSocketConnectionClosedException(
|
||||||
"Connection is already closed.")
|
"Connection is already closed.")
|
||||||
|
|
||||||
def close(self, **kwargs):
|
def close(self, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Close websocket connection.
|
Close websocket connection.
|
||||||
"""
|
"""
|
||||||
|
@ -249,41 +262,41 @@ class WebSocketApp:
|
||||||
self.sock.close(**kwargs)
|
self.sock.close(**kwargs)
|
||||||
self.sock = None
|
self.sock = None
|
||||||
|
|
||||||
def _start_ping_thread(self):
|
def _start_ping_thread(self) -> None:
|
||||||
self.last_ping_tm = self.last_pong_tm = 0
|
self.last_ping_tm = self.last_pong_tm = 0
|
||||||
self.stop_ping = threading.Event()
|
self.stop_ping = threading.Event()
|
||||||
self.ping_thread = threading.Thread(target=self._send_ping)
|
self.ping_thread = threading.Thread(target=self._send_ping)
|
||||||
self.ping_thread.daemon = True
|
self.ping_thread.daemon = True
|
||||||
self.ping_thread.start()
|
self.ping_thread.start()
|
||||||
|
|
||||||
def _stop_ping_thread(self):
|
def _stop_ping_thread(self) -> None:
|
||||||
if self.stop_ping:
|
if self.stop_ping:
|
||||||
self.stop_ping.set()
|
self.stop_ping.set()
|
||||||
if self.ping_thread and self.ping_thread.is_alive():
|
if self.ping_thread and self.ping_thread.is_alive():
|
||||||
self.ping_thread.join(3)
|
self.ping_thread.join(3)
|
||||||
self.last_ping_tm = self.last_pong_tm = 0
|
self.last_ping_tm = self.last_pong_tm = 0
|
||||||
|
|
||||||
def _send_ping(self):
|
def _send_ping(self) -> None:
|
||||||
if self.stop_ping.wait(self.ping_interval):
|
if self.stop_ping.wait(self.ping_interval) or self.keep_running is False:
|
||||||
return
|
return
|
||||||
while not self.stop_ping.wait(self.ping_interval):
|
while not self.stop_ping.wait(self.ping_interval) and self.keep_running is True:
|
||||||
if self.sock:
|
if self.sock:
|
||||||
self.last_ping_tm = time.time()
|
self.last_ping_tm = time.time()
|
||||||
try:
|
try:
|
||||||
_logging.debug("Sending ping")
|
_logging.debug("Sending ping")
|
||||||
self.sock.ping(self.ping_payload)
|
self.sock.ping(self.ping_payload)
|
||||||
except Exception as ex:
|
except Exception as e:
|
||||||
_logging.debug("Failed to send ping: %s", ex)
|
_logging.debug("Failed to send ping: {err}".format(err=e))
|
||||||
|
|
||||||
def run_forever(self, sockopt=None, sslopt=None,
|
def run_forever(self, sockopt: tuple = None, sslopt: dict = None,
|
||||||
ping_interval=0, ping_timeout=None,
|
ping_interval: float = 0, ping_timeout: float or None = None,
|
||||||
ping_payload="",
|
ping_payload: str = "",
|
||||||
http_proxy_host=None, http_proxy_port=None,
|
http_proxy_host: str = None, http_proxy_port: int or str = None,
|
||||||
http_no_proxy=None, http_proxy_auth=None,
|
http_no_proxy: list = None, http_proxy_auth: tuple = None,
|
||||||
http_proxy_timeout=None,
|
http_proxy_timeout: float = None,
|
||||||
skip_utf8_validation=False,
|
skip_utf8_validation: bool = False,
|
||||||
host=None, origin=None, dispatcher=None,
|
host: str = None, origin: str = None, dispatcher: Dispatcher = None,
|
||||||
suppress_origin=False, proxy_type=None, reconnect=None):
|
suppress_origin: bool = False, proxy_type: str = None, reconnect: int = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Run event loop for WebSocket framework.
|
Run event loop for WebSocket framework.
|
||||||
|
|
||||||
|
@ -358,7 +371,7 @@ class WebSocketApp:
|
||||||
self.ping_payload = ping_payload
|
self.ping_payload = ping_payload
|
||||||
self.keep_running = True
|
self.keep_running = True
|
||||||
|
|
||||||
def teardown(close_frame=None):
|
def teardown(close_frame: ABNF = None):
|
||||||
"""
|
"""
|
||||||
Tears down the connection.
|
Tears down the connection.
|
||||||
|
|
||||||
|
@ -369,6 +382,13 @@ class WebSocketApp:
|
||||||
with the statusCode and reason from the provided frame.
|
with the statusCode and reason from the provided frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# teardown() is called in many code paths to ensure resources are cleaned up and on_close is fired.
|
||||||
|
# To ensure the work is only done once, we use this bool and lock.
|
||||||
|
with self.has_done_teardown_lock:
|
||||||
|
if self.has_done_teardown:
|
||||||
|
return
|
||||||
|
self.has_done_teardown = True
|
||||||
|
|
||||||
self._stop_ping_thread()
|
self._stop_ping_thread()
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
if self.sock:
|
if self.sock:
|
||||||
|
@ -380,7 +400,7 @@ class WebSocketApp:
|
||||||
# Finally call the callback AFTER all teardown is complete
|
# Finally call the callback AFTER all teardown is complete
|
||||||
self._callback(self.on_close, close_status_code, close_reason)
|
self._callback(self.on_close, close_status_code, close_reason)
|
||||||
|
|
||||||
def setSock(reconnecting=False):
|
def setSock(reconnecting: bool = False) -> None:
|
||||||
if reconnecting and self.sock:
|
if reconnecting and self.sock:
|
||||||
self.sock.shutdown()
|
self.sock.shutdown()
|
||||||
|
|
||||||
|
@ -392,8 +412,11 @@ class WebSocketApp:
|
||||||
|
|
||||||
self.sock.settimeout(getdefaulttimeout())
|
self.sock.settimeout(getdefaulttimeout())
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
header = self.header() if callable(self.header) else self.header
|
||||||
|
|
||||||
self.sock.connect(
|
self.sock.connect(
|
||||||
self.url, header=self.header, cookie=self.cookie,
|
self.url, header=header, cookie=self.cookie,
|
||||||
http_proxy_host=http_proxy_host,
|
http_proxy_host=http_proxy_host,
|
||||||
http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy,
|
http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy,
|
||||||
http_proxy_auth=http_proxy_auth, http_proxy_timeout=http_proxy_timeout,
|
http_proxy_auth=http_proxy_auth, http_proxy_timeout=http_proxy_timeout,
|
||||||
|
@ -412,7 +435,7 @@ class WebSocketApp:
|
||||||
except (WebSocketConnectionClosedException, ConnectionRefusedError, KeyboardInterrupt, SystemExit, Exception) as e:
|
except (WebSocketConnectionClosedException, ConnectionRefusedError, KeyboardInterrupt, SystemExit, Exception) as e:
|
||||||
handleDisconnect(e, reconnecting)
|
handleDisconnect(e, reconnecting)
|
||||||
|
|
||||||
def read():
|
def read() -> bool:
|
||||||
if not self.keep_running:
|
if not self.keep_running:
|
||||||
return teardown()
|
return teardown()
|
||||||
|
|
||||||
|
@ -445,7 +468,7 @@ class WebSocketApp:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check():
|
def check() -> bool:
|
||||||
if (self.ping_timeout):
|
if (self.ping_timeout):
|
||||||
has_timeout_expired = time.time() - self.last_ping_tm > self.ping_timeout
|
has_timeout_expired = time.time() - self.last_ping_tm > self.ping_timeout
|
||||||
has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
|
has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
|
||||||
|
@ -457,7 +480,7 @@ class WebSocketApp:
|
||||||
raise WebSocketTimeoutException("ping/pong timed out")
|
raise WebSocketTimeoutException("ping/pong timed out")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handleDisconnect(e, reconnecting=False):
|
def handleDisconnect(e: Exception, reconnecting: bool = False) -> bool:
|
||||||
self.has_errored = True
|
self.has_errored = True
|
||||||
self._stop_ping_thread()
|
self._stop_ping_thread()
|
||||||
if not reconnecting:
|
if not reconnecting:
|
||||||
|
@ -469,26 +492,34 @@ class WebSocketApp:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if reconnect:
|
if reconnect:
|
||||||
_logging.info("%s - reconnect" % e)
|
_logging.info("{err} - reconnect".format(err=e))
|
||||||
if custom_dispatcher:
|
if custom_dispatcher:
|
||||||
_logging.debug("Calling custom dispatcher reconnect [%s frames in stack]" % len(inspect.stack()))
|
_logging.debug("Calling custom dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack())))
|
||||||
dispatcher.reconnect(reconnect, setSock)
|
dispatcher.reconnect(reconnect, setSock)
|
||||||
else:
|
else:
|
||||||
_logging.error("%s - goodbye" % e)
|
_logging.error("{err} - goodbye".format(err=e))
|
||||||
teardown()
|
teardown()
|
||||||
|
|
||||||
custom_dispatcher = bool(dispatcher)
|
custom_dispatcher = bool(dispatcher)
|
||||||
dispatcher = self.create_dispatcher(ping_timeout, dispatcher, parse_url(self.url)[3])
|
dispatcher = self.create_dispatcher(ping_timeout, dispatcher, parse_url(self.url)[3])
|
||||||
|
|
||||||
|
try:
|
||||||
setSock()
|
setSock()
|
||||||
if not custom_dispatcher and reconnect:
|
if not custom_dispatcher and reconnect:
|
||||||
while self.keep_running:
|
while self.keep_running:
|
||||||
_logging.debug("Calling dispatcher reconnect [%s frames in stack]" % len(inspect.stack()))
|
_logging.debug("Calling dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack())))
|
||||||
dispatcher.reconnect(reconnect, setSock)
|
dispatcher.reconnect(reconnect, setSock)
|
||||||
|
except (KeyboardInterrupt, Exception) as e:
|
||||||
|
_logging.info("tearing down on exception {err}".format(err=e))
|
||||||
|
teardown()
|
||||||
|
finally:
|
||||||
|
if not custom_dispatcher:
|
||||||
|
# Ensure teardown was called before returning from run_forever
|
||||||
|
teardown()
|
||||||
|
|
||||||
return self.has_errored
|
return self.has_errored
|
||||||
|
|
||||||
def create_dispatcher(self, ping_timeout, dispatcher=None, is_ssl=False):
|
def create_dispatcher(self, ping_timeout: int, dispatcher: Dispatcher = None, is_ssl: bool = False) -> DispatcherBase:
|
||||||
if dispatcher: # If custom dispatcher is set, use WrappedDispatcher
|
if dispatcher: # If custom dispatcher is set, use WrappedDispatcher
|
||||||
return WrappedDispatcher(self, ping_timeout, dispatcher)
|
return WrappedDispatcher(self, ping_timeout, dispatcher)
|
||||||
timeout = ping_timeout or 10
|
timeout = ping_timeout or 10
|
||||||
|
@ -497,7 +528,7 @@ class WebSocketApp:
|
||||||
|
|
||||||
return Dispatcher(self, timeout)
|
return Dispatcher(self, timeout)
|
||||||
|
|
||||||
def _get_close_args(self, close_frame):
|
def _get_close_args(self, close_frame: ABNF) -> list:
|
||||||
"""
|
"""
|
||||||
_get_close_args extracts the close code and reason from the close body
|
_get_close_args extracts the close code and reason from the close body
|
||||||
if it exists (RFC6455 says WebSocket Connection Close Code is optional)
|
if it exists (RFC6455 says WebSocket Connection Close Code is optional)
|
||||||
|
@ -516,12 +547,12 @@ class WebSocketApp:
|
||||||
# Most likely reached this because len(close_frame_data.data) < 2
|
# Most likely reached this because len(close_frame_data.data) < 2
|
||||||
return [None, None]
|
return [None, None]
|
||||||
|
|
||||||
def _callback(self, callback, *args):
|
def _callback(self, callback, *args) -> None:
|
||||||
if callback:
|
if callback:
|
||||||
try:
|
try:
|
||||||
callback(self, *args)
|
callback(self, *args)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logging.error("error from callback {}: {}".format(callback, e))
|
_logging.error("error from callback {callback}: {err}".format(callback=callback, err=e))
|
||||||
if self.on_error:
|
if self.on_error:
|
||||||
self.on_error(self, e)
|
self.on_error(self, e)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import http.cookies
|
||||||
_cookiejar.py
|
_cookiejar.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,10 +21,10 @@ limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
class SimpleCookieJar:
|
class SimpleCookieJar:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.jar = dict()
|
self.jar = dict()
|
||||||
|
|
||||||
def add(self, set_cookie):
|
def add(self, set_cookie: str) -> None:
|
||||||
if set_cookie:
|
if set_cookie:
|
||||||
simpleCookie = http.cookies.SimpleCookie(set_cookie)
|
simpleCookie = http.cookies.SimpleCookie(set_cookie)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class SimpleCookieJar:
|
||||||
cookie.update(simpleCookie)
|
cookie.update(simpleCookie)
|
||||||
self.jar[domain.lower()] = cookie
|
self.jar[domain.lower()] = cookie
|
||||||
|
|
||||||
def set(self, set_cookie):
|
def set(self, set_cookie: str) -> None:
|
||||||
if set_cookie:
|
if set_cookie:
|
||||||
simpleCookie = http.cookies.SimpleCookie(set_cookie)
|
simpleCookie = http.cookies.SimpleCookie(set_cookie)
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class SimpleCookieJar:
|
||||||
domain = "." + domain
|
domain = "." + domain
|
||||||
self.jar[domain.lower()] = simpleCookie
|
self.jar[domain.lower()] = simpleCookie
|
||||||
|
|
||||||
def get(self, host):
|
def get(self, host: str) -> str:
|
||||||
if not host:
|
if not host:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from ._utils import *
|
||||||
_core.py
|
_core.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -74,8 +74,8 @@ class WebSocket:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
|
def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
|
||||||
fire_cont_frame=False, enable_multithread=True,
|
fire_cont_frame: bool = False, enable_multithread: bool = True,
|
||||||
skip_utf8_validation=False, **_):
|
skip_utf8_validation: bool = False, **_):
|
||||||
"""
|
"""
|
||||||
Initialize WebSocket object.
|
Initialize WebSocket object.
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ class WebSocket:
|
||||||
"""
|
"""
|
||||||
self.get_mask_key = func
|
self.get_mask_key = func
|
||||||
|
|
||||||
def gettimeout(self):
|
def gettimeout(self) -> float:
|
||||||
"""
|
"""
|
||||||
Get the websocket timeout (in seconds) as an int or float
|
Get the websocket timeout (in seconds) as an int or float
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ class WebSocket:
|
||||||
"""
|
"""
|
||||||
return self.sock_opt.timeout
|
return self.sock_opt.timeout
|
||||||
|
|
||||||
def settimeout(self, timeout):
|
def settimeout(self, timeout: float):
|
||||||
"""
|
"""
|
||||||
Set the timeout to the websocket.
|
Set the timeout to the websocket.
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ class WebSocket:
|
||||||
self.sock = None
|
self.sock = None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def send(self, payload, opcode=ABNF.OPCODE_TEXT):
|
def send(self, payload: bytes or str, opcode: int = ABNF.OPCODE_TEXT) -> int:
|
||||||
"""
|
"""
|
||||||
Send the data as string.
|
Send the data as string.
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ class WebSocket:
|
||||||
frame = ABNF.create_frame(payload, opcode)
|
frame = ABNF.create_frame(payload, opcode)
|
||||||
return self.send_frame(frame)
|
return self.send_frame(frame)
|
||||||
|
|
||||||
def send_frame(self, frame):
|
def send_frame(self, frame) -> int:
|
||||||
"""
|
"""
|
||||||
Send the data frame.
|
Send the data frame.
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ class WebSocket:
|
||||||
|
|
||||||
return length
|
return length
|
||||||
|
|
||||||
def send_binary(self, payload):
|
def send_binary(self, payload: bytes) -> int:
|
||||||
"""
|
"""
|
||||||
Send a binary message (OPCODE_BINARY).
|
Send a binary message (OPCODE_BINARY).
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ class WebSocket:
|
||||||
"""
|
"""
|
||||||
return self.send(payload, ABNF.OPCODE_BINARY)
|
return self.send(payload, ABNF.OPCODE_BINARY)
|
||||||
|
|
||||||
def ping(self, payload=""):
|
def ping(self, payload: str or bytes = ""):
|
||||||
"""
|
"""
|
||||||
Send ping data.
|
Send ping data.
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ class WebSocket:
|
||||||
payload = payload.encode("utf-8")
|
payload = payload.encode("utf-8")
|
||||||
self.send(payload, ABNF.OPCODE_PING)
|
self.send(payload, ABNF.OPCODE_PING)
|
||||||
|
|
||||||
def pong(self, payload=""):
|
def pong(self, payload: str or bytes = ""):
|
||||||
"""
|
"""
|
||||||
Send pong data.
|
Send pong data.
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ class WebSocket:
|
||||||
payload = payload.encode("utf-8")
|
payload = payload.encode("utf-8")
|
||||||
self.send(payload, ABNF.OPCODE_PONG)
|
self.send(payload, ABNF.OPCODE_PONG)
|
||||||
|
|
||||||
def recv(self):
|
def recv(self) -> str or bytes:
|
||||||
"""
|
"""
|
||||||
Receive string data(byte array) from the server.
|
Receive string data(byte array) from the server.
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ class WebSocket:
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def recv_data(self, control_frame=False):
|
def recv_data(self, control_frame: bool = False) -> tuple:
|
||||||
"""
|
"""
|
||||||
Receive data with operation code.
|
Receive data with operation code.
|
||||||
|
|
||||||
|
@ -385,7 +385,7 @@ class WebSocket:
|
||||||
opcode, frame = self.recv_data_frame(control_frame)
|
opcode, frame = self.recv_data_frame(control_frame)
|
||||||
return opcode, frame.data
|
return opcode, frame.data
|
||||||
|
|
||||||
def recv_data_frame(self, control_frame=False):
|
def recv_data_frame(self, control_frame: bool = False):
|
||||||
"""
|
"""
|
||||||
Receive data with operation code.
|
Receive data with operation code.
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ class WebSocket:
|
||||||
# handle error:
|
# handle error:
|
||||||
# 'NoneType' object has no attribute 'opcode'
|
# 'NoneType' object has no attribute 'opcode'
|
||||||
raise WebSocketProtocolException(
|
raise WebSocketProtocolException(
|
||||||
"Not a valid frame %s" % frame)
|
"Not a valid frame {frame}".format(frame=frame))
|
||||||
elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
|
elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
|
||||||
self.cont_frame.validate(frame)
|
self.cont_frame.validate(frame)
|
||||||
self.cont_frame.add(frame)
|
self.cont_frame.add(frame)
|
||||||
|
@ -444,7 +444,7 @@ class WebSocket:
|
||||||
"""
|
"""
|
||||||
return self.frame_buffer.recv_frame()
|
return self.frame_buffer.recv_frame()
|
||||||
|
|
||||||
def send_close(self, status=STATUS_NORMAL, reason=b""):
|
def send_close(self, status: int = STATUS_NORMAL, reason: bytes = b""):
|
||||||
"""
|
"""
|
||||||
Send close data to the server.
|
Send close data to the server.
|
||||||
|
|
||||||
|
@ -460,14 +460,14 @@ class WebSocket:
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
|
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
|
||||||
|
|
||||||
def close(self, status=STATUS_NORMAL, reason=b"", timeout=3):
|
def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: float = 3):
|
||||||
"""
|
"""
|
||||||
Close Websocket object
|
Close Websocket object
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
status: int
|
status: int
|
||||||
Status code to send. See STATUS_XXX.
|
Status code to send. See VALID_CLOSE_STATUS in ABNF.
|
||||||
reason: bytes
|
reason: bytes
|
||||||
The reason to close in UTF-8.
|
The reason to close in UTF-8.
|
||||||
timeout: int or float
|
timeout: int or float
|
||||||
|
@ -521,7 +521,7 @@ class WebSocket:
|
||||||
self.sock = None
|
self.sock = None
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data: str or bytes):
|
||||||
return send(self.sock, data)
|
return send(self.sock, data)
|
||||||
|
|
||||||
def _recv(self, bufsize):
|
def _recv(self, bufsize):
|
||||||
|
@ -535,7 +535,7 @@ class WebSocket:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def create_connection(url, timeout=None, class_=WebSocket, **options):
|
def create_connection(url: str, timeout=None, class_=WebSocket, **options):
|
||||||
"""
|
"""
|
||||||
Connect to url and return websocket object.
|
Connect to url and return websocket object.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
_exceptions.py
|
_exceptions.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -66,11 +66,11 @@ class WebSocketBadStatusException(WebSocketException):
|
||||||
WebSocketBadStatusException will be raised when we get bad handshake status code.
|
WebSocketBadStatusException will be raised when we get bad handshake status code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message, status_code, status_message=None, resp_headers=None):
|
def __init__(self, message: str, status_code: int, status_message=None, resp_headers=None, resp_body=None):
|
||||||
msg = message % (status_code, status_message)
|
super().__init__(message)
|
||||||
super().__init__(msg)
|
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
self.resp_headers = resp_headers
|
self.resp_headers = resp_headers
|
||||||
|
self.resp_body = resp_body
|
||||||
|
|
||||||
|
|
||||||
class WebSocketAddressException(WebSocketException):
|
class WebSocketAddressException(WebSocketException):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
_handshake.py
|
_handshake.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -40,14 +40,14 @@ CookieJar = SimpleCookieJar()
|
||||||
|
|
||||||
class handshake_response:
|
class handshake_response:
|
||||||
|
|
||||||
def __init__(self, status, headers, subprotocol):
|
def __init__(self, status: int, headers: dict, subprotocol):
|
||||||
self.status = status
|
self.status = status
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
self.subprotocol = subprotocol
|
self.subprotocol = subprotocol
|
||||||
CookieJar.add(headers.get("set-cookie"))
|
CookieJar.add(headers.get("set-cookie"))
|
||||||
|
|
||||||
|
|
||||||
def handshake(sock, url, hostname, port, resource, **options):
|
def handshake(sock, url: str, hostname: str, port: int, resource: str, **options):
|
||||||
headers, key = _get_handshake_headers(resource, url, hostname, port, options)
|
headers, key = _get_handshake_headers(resource, url, hostname, port, options)
|
||||||
|
|
||||||
header_str = "\r\n".join(headers)
|
header_str = "\r\n".join(headers)
|
||||||
|
@ -64,7 +64,7 @@ def handshake(sock, url, hostname, port, resource, **options):
|
||||||
return handshake_response(status, resp, subproto)
|
return handshake_response(status, resp, subproto)
|
||||||
|
|
||||||
|
|
||||||
def _pack_hostname(hostname):
|
def _pack_hostname(hostname: str) -> str:
|
||||||
# IPv6 address
|
# IPv6 address
|
||||||
if ':' in hostname:
|
if ':' in hostname:
|
||||||
return '[' + hostname + ']'
|
return '[' + hostname + ']'
|
||||||
|
@ -72,41 +72,41 @@ def _pack_hostname(hostname):
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
|
|
||||||
def _get_handshake_headers(resource, url, host, port, options):
|
def _get_handshake_headers(resource: str, url: str, host: str, port: int, options: dict):
|
||||||
headers = [
|
headers = [
|
||||||
"GET %s HTTP/1.1" % resource,
|
"GET {resource} HTTP/1.1".format(resource=resource),
|
||||||
"Upgrade: websocket"
|
"Upgrade: websocket"
|
||||||
]
|
]
|
||||||
if port == 80 or port == 443:
|
if port == 80 or port == 443:
|
||||||
hostport = _pack_hostname(host)
|
hostport = _pack_hostname(host)
|
||||||
else:
|
else:
|
||||||
hostport = "%s:%d" % (_pack_hostname(host), port)
|
hostport = "{h}:{p}".format(h=_pack_hostname(host), p=port)
|
||||||
if options.get("host"):
|
if options.get("host"):
|
||||||
headers.append("Host: %s" % options["host"])
|
headers.append("Host: {h}".format(h=options["host"]))
|
||||||
else:
|
else:
|
||||||
headers.append("Host: %s" % hostport)
|
headers.append("Host: {hp}".format(hp=hostport))
|
||||||
|
|
||||||
# scheme indicates whether http or https is used in Origin
|
# scheme indicates whether http or https is used in Origin
|
||||||
# The same approach is used in parse_url of _url.py to set default port
|
# The same approach is used in parse_url of _url.py to set default port
|
||||||
scheme, url = url.split(":", 1)
|
scheme, url = url.split(":", 1)
|
||||||
if not options.get("suppress_origin"):
|
if not options.get("suppress_origin"):
|
||||||
if "origin" in options and options["origin"] is not None:
|
if "origin" in options and options["origin"] is not None:
|
||||||
headers.append("Origin: %s" % options["origin"])
|
headers.append("Origin: {origin}".format(origin=options["origin"]))
|
||||||
elif scheme == "wss":
|
elif scheme == "wss":
|
||||||
headers.append("Origin: https://%s" % hostport)
|
headers.append("Origin: https://{hp}".format(hp=hostport))
|
||||||
else:
|
else:
|
||||||
headers.append("Origin: http://%s" % hostport)
|
headers.append("Origin: http://{hp}".format(hp=hostport))
|
||||||
|
|
||||||
key = _create_sec_websocket_key()
|
key = _create_sec_websocket_key()
|
||||||
|
|
||||||
# Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
|
# Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
|
||||||
if not options.get('header') or 'Sec-WebSocket-Key' not in options['header']:
|
if not options.get('header') or 'Sec-WebSocket-Key' not in options['header']:
|
||||||
headers.append("Sec-WebSocket-Key: %s" % key)
|
headers.append("Sec-WebSocket-Key: {key}".format(key=key))
|
||||||
else:
|
else:
|
||||||
key = options['header']['Sec-WebSocket-Key']
|
key = options['header']['Sec-WebSocket-Key']
|
||||||
|
|
||||||
if not options.get('header') or 'Sec-WebSocket-Version' not in options['header']:
|
if not options.get('header') or 'Sec-WebSocket-Version' not in options['header']:
|
||||||
headers.append("Sec-WebSocket-Version: %s" % VERSION)
|
headers.append("Sec-WebSocket-Version: {version}".format(version=VERSION))
|
||||||
|
|
||||||
if not options.get('connection'):
|
if not options.get('connection'):
|
||||||
headers.append('Connection: Upgrade')
|
headers.append('Connection: Upgrade')
|
||||||
|
@ -115,7 +115,7 @@ def _get_handshake_headers(resource, url, host, port, options):
|
||||||
|
|
||||||
subprotocols = options.get("subprotocols")
|
subprotocols = options.get("subprotocols")
|
||||||
if subprotocols:
|
if subprotocols:
|
||||||
headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols))
|
headers.append("Sec-WebSocket-Protocol: {protocols}".format(protocols=",".join(subprotocols)))
|
||||||
|
|
||||||
header = options.get("header")
|
header = options.get("header")
|
||||||
if header:
|
if header:
|
||||||
|
@ -133,18 +133,21 @@ def _get_handshake_headers(resource, url, host, port, options):
|
||||||
cookie = "; ".join(filter(None, [server_cookie, client_cookie]))
|
cookie = "; ".join(filter(None, [server_cookie, client_cookie]))
|
||||||
|
|
||||||
if cookie:
|
if cookie:
|
||||||
headers.append("Cookie: %s" % cookie)
|
headers.append("Cookie: {cookie}".format(cookie=cookie))
|
||||||
|
|
||||||
headers.append("")
|
|
||||||
headers.append("")
|
|
||||||
|
|
||||||
|
headers.extend(("", ""))
|
||||||
return headers, key
|
return headers, key
|
||||||
|
|
||||||
|
|
||||||
def _get_resp_headers(sock, success_statuses=SUCCESS_STATUSES):
|
def _get_resp_headers(sock, success_statuses: tuple = SUCCESS_STATUSES) -> tuple:
|
||||||
status, resp_headers, status_message = read_headers(sock)
|
status, resp_headers, status_message = read_headers(sock)
|
||||||
if status not in success_statuses:
|
if status not in success_statuses:
|
||||||
raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
|
content_len = resp_headers.get('content-length')
|
||||||
|
if content_len:
|
||||||
|
response_body = sock.recv(int(content_len)) # read the body of the HTTP error message response and include it in the exception
|
||||||
|
else:
|
||||||
|
response_body = None
|
||||||
|
raise WebSocketBadStatusException("Handshake status {status} {message} -+-+- {headers} -+-+- {body}".format(status=status, message=status_message, headers=resp_headers, body=response_body), status, status_message, resp_headers, response_body)
|
||||||
return status, resp_headers
|
return status, resp_headers
|
||||||
|
|
||||||
|
|
||||||
|
@ -154,7 +157,7 @@ _HEADERS_TO_CHECK = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _validate(headers, key, subprotocols):
|
def _validate(headers, key: str, subprotocols):
|
||||||
subproto = None
|
subproto = None
|
||||||
for k, v in _HEADERS_TO_CHECK.items():
|
for k, v in _HEADERS_TO_CHECK.items():
|
||||||
r = headers.get(k, None)
|
r = headers.get(k, None)
|
||||||
|
@ -189,6 +192,6 @@ def _validate(headers, key, subprotocols):
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
def _create_sec_websocket_key():
|
def _create_sec_websocket_key() -> str:
|
||||||
randomness = os.urandom(16)
|
randomness = os.urandom(16)
|
||||||
return base64encode(randomness).decode('utf-8').strip()
|
return base64encode(randomness).decode('utf-8').strip()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
_http.py
|
_http.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -19,7 +19,6 @@ limitations under the License.
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
|
||||||
|
|
||||||
from ._exceptions import *
|
from ._exceptions import *
|
||||||
from ._logging import *
|
from ._logging import *
|
||||||
|
@ -69,7 +68,7 @@ class proxy_info:
|
||||||
self.proxy_protocol = "http"
|
self.proxy_protocol = "http"
|
||||||
|
|
||||||
|
|
||||||
def _start_proxied_socket(url, options, proxy):
|
def _start_proxied_socket(url: str, options, proxy):
|
||||||
if not HAVE_PYTHON_SOCKS:
|
if not HAVE_PYTHON_SOCKS:
|
||||||
raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available")
|
raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available")
|
||||||
|
|
||||||
|
@ -107,7 +106,7 @@ def _start_proxied_socket(url, options, proxy):
|
||||||
return sock, (hostname, port, resource)
|
return sock, (hostname, port, resource)
|
||||||
|
|
||||||
|
|
||||||
def connect(url, options, proxy, socket):
|
def connect(url: str, options, proxy, socket):
|
||||||
# Use _start_proxied_socket() only for socks4 or socks5 proxy
|
# Use _start_proxied_socket() only for socks4 or socks5 proxy
|
||||||
# Use _tunnel() for http proxy
|
# Use _tunnel() for http proxy
|
||||||
# TODO: Use python-socks for http protocol also, to standardize flow
|
# TODO: Use python-socks for http protocol also, to standardize flow
|
||||||
|
@ -211,6 +210,11 @@ def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
|
||||||
context = sslopt.get('context', None)
|
context = sslopt.get('context', None)
|
||||||
if not context:
|
if not context:
|
||||||
context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS_CLIENT))
|
context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS_CLIENT))
|
||||||
|
# Non default context need to manually enable SSLKEYLOGFILE support by setting the keylog_filename attribute.
|
||||||
|
# For more details see also:
|
||||||
|
# * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#context-creation
|
||||||
|
# * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#ssl.SSLContext.keylog_filename
|
||||||
|
context.keylog_filename = os.environ.get("SSLKEYLOGFILE", None)
|
||||||
|
|
||||||
if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
|
if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
|
||||||
cafile = sslopt.get('ca_certs', None)
|
cafile = sslopt.get('ca_certs', None)
|
||||||
|
@ -275,8 +279,8 @@ def _ssl_socket(sock, user_sslopt, hostname):
|
||||||
|
|
||||||
def _tunnel(sock, host, port, auth):
|
def _tunnel(sock, host, port, auth):
|
||||||
debug("Connecting proxy...")
|
debug("Connecting proxy...")
|
||||||
connect_header = "CONNECT %s:%d HTTP/1.1\r\n" % (host, port)
|
connect_header = "CONNECT {h}:{p} HTTP/1.1\r\n".format(h=host, p=port)
|
||||||
connect_header += "Host: %s:%d\r\n" % (host, port)
|
connect_header += "Host: {h}:{p}\r\n".format(h=host, p=port)
|
||||||
|
|
||||||
# TODO: support digest auth.
|
# TODO: support digest auth.
|
||||||
if auth and auth[0]:
|
if auth and auth[0]:
|
||||||
|
@ -284,7 +288,7 @@ def _tunnel(sock, host, port, auth):
|
||||||
if auth[1]:
|
if auth[1]:
|
||||||
auth_str += ":" + auth[1]
|
auth_str += ":" + auth[1]
|
||||||
encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '')
|
encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '')
|
||||||
connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str
|
connect_header += "Proxy-Authorization: Basic {str}\r\n".format(str=encoded_str)
|
||||||
connect_header += "\r\n"
|
connect_header += "\r\n"
|
||||||
dump("request header", connect_header)
|
dump("request header", connect_header)
|
||||||
|
|
||||||
|
@ -297,7 +301,7 @@ def _tunnel(sock, host, port, auth):
|
||||||
|
|
||||||
if status != 200:
|
if status != 200:
|
||||||
raise WebSocketProxyException(
|
raise WebSocketProxyException(
|
||||||
"failed CONNECT via proxy status: %r" % status)
|
"failed CONNECT via proxy status: {status}".format(status=status))
|
||||||
|
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
_logging.py
|
_logging.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -24,7 +24,7 @@ try:
|
||||||
from logging import NullHandler
|
from logging import NullHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
def emit(self, record):
|
def emit(self, record) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
_logger.addHandler(NullHandler())
|
_logger.addHandler(NullHandler())
|
||||||
|
@ -35,7 +35,9 @@ __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
|
||||||
"isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
|
"isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
|
||||||
|
|
||||||
|
|
||||||
def enableTrace(traceable, handler=logging.StreamHandler(), level="DEBUG"):
|
def enableTrace(traceable: bool,
|
||||||
|
handler: logging.StreamHandler = logging.StreamHandler(),
|
||||||
|
level: str = "DEBUG") -> None:
|
||||||
"""
|
"""
|
||||||
Turn on/off the traceability.
|
Turn on/off the traceability.
|
||||||
|
|
||||||
|
@ -51,41 +53,41 @@ def enableTrace(traceable, handler=logging.StreamHandler(), level="DEBUG"):
|
||||||
_logger.setLevel(getattr(logging, level))
|
_logger.setLevel(getattr(logging, level))
|
||||||
|
|
||||||
|
|
||||||
def dump(title, message):
|
def dump(title: str, message: str) -> None:
|
||||||
if _traceEnabled:
|
if _traceEnabled:
|
||||||
_logger.debug("--- " + title + " ---")
|
_logger.debug("--- " + title + " ---")
|
||||||
_logger.debug(message)
|
_logger.debug(message)
|
||||||
_logger.debug("-----------------------")
|
_logger.debug("-----------------------")
|
||||||
|
|
||||||
|
|
||||||
def error(msg):
|
def error(msg: str) -> None:
|
||||||
_logger.error(msg)
|
_logger.error(msg)
|
||||||
|
|
||||||
|
|
||||||
def warning(msg):
|
def warning(msg: str) -> None:
|
||||||
_logger.warning(msg)
|
_logger.warning(msg)
|
||||||
|
|
||||||
|
|
||||||
def debug(msg):
|
def debug(msg: str) -> None:
|
||||||
_logger.debug(msg)
|
_logger.debug(msg)
|
||||||
|
|
||||||
|
|
||||||
def info(msg):
|
def info(msg: str) -> None:
|
||||||
_logger.info(msg)
|
_logger.info(msg)
|
||||||
|
|
||||||
|
|
||||||
def trace(msg):
|
def trace(msg: str) -> None:
|
||||||
if _traceEnabled:
|
if _traceEnabled:
|
||||||
_logger.debug(msg)
|
_logger.debug(msg)
|
||||||
|
|
||||||
|
|
||||||
def isEnabledForError():
|
def isEnabledForError() -> bool:
|
||||||
return _logger.isEnabledFor(logging.ERROR)
|
return _logger.isEnabledFor(logging.ERROR)
|
||||||
|
|
||||||
|
|
||||||
def isEnabledForDebug():
|
def isEnabledForDebug() -> bool:
|
||||||
return _logger.isEnabledFor(logging.DEBUG)
|
return _logger.isEnabledFor(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
def isEnabledForTrace():
|
def isEnabledForTrace() -> bool:
|
||||||
return _traceEnabled
|
return _traceEnabled
|
||||||
|
|
|
@ -10,7 +10,7 @@ from ._utils import *
|
||||||
_socket.py
|
_socket.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -43,7 +43,7 @@ __all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefault
|
||||||
|
|
||||||
class sock_opt:
|
class sock_opt:
|
||||||
|
|
||||||
def __init__(self, sockopt, sslopt):
|
def __init__(self, sockopt: list, sslopt: dict) -> None:
|
||||||
if sockopt is None:
|
if sockopt is None:
|
||||||
sockopt = []
|
sockopt = []
|
||||||
if sslopt is None:
|
if sslopt is None:
|
||||||
|
@ -53,7 +53,7 @@ class sock_opt:
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
|
|
||||||
def setdefaulttimeout(timeout):
|
def setdefaulttimeout(timeout: int or float) -> None:
|
||||||
"""
|
"""
|
||||||
Set the global timeout setting to connect.
|
Set the global timeout setting to connect.
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ def setdefaulttimeout(timeout):
|
||||||
_default_timeout = timeout
|
_default_timeout = timeout
|
||||||
|
|
||||||
|
|
||||||
def getdefaulttimeout():
|
def getdefaulttimeout() -> int or float:
|
||||||
"""
|
"""
|
||||||
Get default timeout
|
Get default timeout
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ def getdefaulttimeout():
|
||||||
return _default_timeout
|
return _default_timeout
|
||||||
|
|
||||||
|
|
||||||
def recv(sock, bufsize):
|
def recv(sock: socket.socket, bufsize: int) -> bytes:
|
||||||
if not sock:
|
if not sock:
|
||||||
raise WebSocketConnectionClosedException("socket is already closed.")
|
raise WebSocketConnectionClosedException("socket is already closed.")
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ def recv(sock, bufsize):
|
||||||
return bytes_
|
return bytes_
|
||||||
|
|
||||||
|
|
||||||
def recv_line(sock):
|
def recv_line(sock: socket.socket) -> bytes:
|
||||||
line = []
|
line = []
|
||||||
while True:
|
while True:
|
||||||
c = recv(sock, 1)
|
c = recv(sock, 1)
|
||||||
|
@ -135,7 +135,7 @@ def recv_line(sock):
|
||||||
return b''.join(line)
|
return b''.join(line)
|
||||||
|
|
||||||
|
|
||||||
def send(sock, data):
|
def send(sock: socket.socket, data: bytes) -> int:
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.encode('utf-8')
|
data = data.encode('utf-8')
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
_ssl_compat.py
|
_ssl_compat.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -8,7 +8,7 @@ from urllib.parse import unquote, urlparse
|
||||||
_url.py
|
_url.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -26,7 +26,7 @@ limitations under the License.
|
||||||
__all__ = ["parse_url", "get_proxy_info"]
|
__all__ = ["parse_url", "get_proxy_info"]
|
||||||
|
|
||||||
|
|
||||||
def parse_url(url):
|
def parse_url(url: str) -> tuple:
|
||||||
"""
|
"""
|
||||||
parse url and the result is tuple of
|
parse url and the result is tuple of
|
||||||
(hostname, port, resource path and the flag of secure mode)
|
(hostname, port, resource path and the flag of secure mode)
|
||||||
|
@ -75,7 +75,7 @@ def parse_url(url):
|
||||||
DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"]
|
DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
|
|
||||||
def _is_ip_address(addr):
|
def _is_ip_address(addr: str) -> bool:
|
||||||
try:
|
try:
|
||||||
socket.inet_aton(addr)
|
socket.inet_aton(addr)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
|
@ -84,7 +84,7 @@ def _is_ip_address(addr):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _is_subnet_address(hostname):
|
def _is_subnet_address(hostname: str) -> bool:
|
||||||
try:
|
try:
|
||||||
addr, netmask = hostname.split("/")
|
addr, netmask = hostname.split("/")
|
||||||
return _is_ip_address(addr) and 0 <= int(netmask) < 32
|
return _is_ip_address(addr) and 0 <= int(netmask) < 32
|
||||||
|
@ -92,7 +92,7 @@ def _is_subnet_address(hostname):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_address_in_network(ip, net):
|
def _is_address_in_network(ip: str, net: str) -> bool:
|
||||||
ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0]
|
ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0]
|
||||||
netaddr, netmask = net.split('/')
|
netaddr, netmask = net.split('/')
|
||||||
netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0]
|
netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0]
|
||||||
|
@ -101,7 +101,7 @@ def _is_address_in_network(ip, net):
|
||||||
return ipaddr & netmask == netaddr
|
return ipaddr & netmask == netaddr
|
||||||
|
|
||||||
|
|
||||||
def _is_no_proxy_host(hostname, no_proxy):
|
def _is_no_proxy_host(hostname: str, no_proxy: list) -> bool:
|
||||||
if not no_proxy:
|
if not no_proxy:
|
||||||
v = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(" ", "")
|
v = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(" ", "")
|
||||||
if v:
|
if v:
|
||||||
|
@ -122,8 +122,8 @@ def _is_no_proxy_host(hostname, no_proxy):
|
||||||
|
|
||||||
|
|
||||||
def get_proxy_info(
|
def get_proxy_info(
|
||||||
hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None,
|
hostname: str, is_secure: bool, proxy_host: str = None, proxy_port: int = 0, proxy_auth: tuple = None,
|
||||||
no_proxy=None, proxy_type='http'):
|
no_proxy: list = None, proxy_type: str = 'http') -> tuple:
|
||||||
"""
|
"""
|
||||||
Try to retrieve proxy host and port from environment
|
Try to retrieve proxy host and port from environment
|
||||||
if not provided in options.
|
if not provided in options.
|
||||||
|
@ -137,14 +137,14 @@ def get_proxy_info(
|
||||||
Websocket server name.
|
Websocket server name.
|
||||||
is_secure: bool
|
is_secure: bool
|
||||||
Is the connection secure? (wss) looks for "https_proxy" in env
|
Is the connection secure? (wss) looks for "https_proxy" in env
|
||||||
before falling back to "http_proxy"
|
instead of "http_proxy"
|
||||||
proxy_host: str
|
proxy_host: str
|
||||||
http proxy host name.
|
http proxy host name.
|
||||||
http_proxy_port: str or int
|
proxy_port: str or int
|
||||||
http proxy port.
|
http proxy port.
|
||||||
http_no_proxy: list
|
no_proxy: list
|
||||||
Whitelisted host names that don't use the proxy.
|
Whitelisted host names that don't use the proxy.
|
||||||
http_proxy_auth: tuple
|
proxy_auth: tuple
|
||||||
HTTP proxy auth information. Tuple of username and password. Default is None.
|
HTTP proxy auth information. Tuple of username and password. Default is None.
|
||||||
proxy_type: str
|
proxy_type: str
|
||||||
Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http".
|
Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http".
|
||||||
|
@ -158,12 +158,8 @@ def get_proxy_info(
|
||||||
auth = proxy_auth
|
auth = proxy_auth
|
||||||
return proxy_host, port, auth
|
return proxy_host, port, auth
|
||||||
|
|
||||||
env_keys = ["http_proxy"]
|
env_key = "https_proxy" if is_secure else "http_proxy"
|
||||||
if is_secure:
|
value = os.environ.get(env_key, os.environ.get(env_key.upper(), "")).replace(" ", "")
|
||||||
env_keys.insert(0, "https_proxy")
|
|
||||||
|
|
||||||
for key in env_keys:
|
|
||||||
value = os.environ.get(key, os.environ.get(key.upper(), "")).replace(" ", "")
|
|
||||||
if value:
|
if value:
|
||||||
proxy = urlparse(value)
|
proxy = urlparse(value)
|
||||||
auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None
|
auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
_url.py
|
_url.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,10 +21,10 @@ __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code
|
||||||
|
|
||||||
class NoLock:
|
class NoLock:
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ try:
|
||||||
# strings.
|
# strings.
|
||||||
from wsaccel.utf8validator import Utf8Validator
|
from wsaccel.utf8validator import Utf8Validator
|
||||||
|
|
||||||
def _validate_utf8(utfbytes):
|
def _validate_utf8(utfbytes: bytes) -> bool:
|
||||||
return Utf8Validator().validate(utfbytes)[0]
|
return Utf8Validator().validate(utfbytes)[0]
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -63,7 +63,7 @@ except ImportError:
|
||||||
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
|
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
|
||||||
12,36,12,12,12,12,12,12,12,12,12,12, ]
|
12,36,12,12,12,12,12,12,12,12,12,12, ]
|
||||||
|
|
||||||
def _decode(state, codep, ch):
|
def _decode(state: int, codep: int, ch: int) -> tuple:
|
||||||
tp = _UTF8D[ch]
|
tp = _UTF8D[ch]
|
||||||
|
|
||||||
codep = (ch & 0x3f) | (codep << 6) if (
|
codep = (ch & 0x3f) | (codep << 6) if (
|
||||||
|
@ -72,7 +72,7 @@ except ImportError:
|
||||||
|
|
||||||
return state, codep
|
return state, codep
|
||||||
|
|
||||||
def _validate_utf8(utfbytes):
|
def _validate_utf8(utfbytes: str or bytes) -> bool:
|
||||||
state = _UTF8_ACCEPT
|
state = _UTF8_ACCEPT
|
||||||
codep = 0
|
codep = 0
|
||||||
for i in utfbytes:
|
for i in utfbytes:
|
||||||
|
@ -83,7 +83,7 @@ except ImportError:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def validate_utf8(utfbytes):
|
def validate_utf8(utfbytes: str or bytes) -> bool:
|
||||||
"""
|
"""
|
||||||
validate utf8 byte string.
|
validate utf8 byte string.
|
||||||
utfbytes: utf byte string to check.
|
utfbytes: utf byte string to check.
|
||||||
|
@ -92,13 +92,13 @@ def validate_utf8(utfbytes):
|
||||||
return _validate_utf8(utfbytes)
|
return _validate_utf8(utfbytes)
|
||||||
|
|
||||||
|
|
||||||
def extract_err_message(exception):
|
def extract_err_message(exception: Exception) -> str or None:
|
||||||
if exception.args:
|
if exception.args:
|
||||||
return exception.args[0]
|
return exception.args[0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def extract_error_code(exception):
|
def extract_error_code(exception: Exception) -> int or None:
|
||||||
if exception.args and len(exception.args) > 1:
|
if exception.args and len(exception.args) > 1:
|
||||||
return exception.args[0] if isinstance(exception.args[0], int) else None
|
return exception.args[0] if isinstance(exception.args[0], int) else None
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
wsdump.py
|
wsdump.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -37,7 +37,7 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_encoding():
|
def get_encoding() -> str:
|
||||||
encoding = getattr(sys.stdin, "encoding", "")
|
encoding = getattr(sys.stdin, "encoding", "")
|
||||||
if not encoding:
|
if not encoding:
|
||||||
return "utf-8"
|
return "utf-8"
|
||||||
|
@ -51,7 +51,7 @@ ENCODING = get_encoding()
|
||||||
|
|
||||||
class VAction(argparse.Action):
|
class VAction(argparse.Action):
|
||||||
|
|
||||||
def __call__(self, parser, args, values, option_string=None):
|
def __call__(self, parser: argparse.Namespace, args: tuple, values: str, option_string: str = None) -> None:
|
||||||
if values is None:
|
if values is None:
|
||||||
values = "1"
|
values = "1"
|
||||||
try:
|
try:
|
||||||
|
@ -61,7 +61,7 @@ class VAction(argparse.Action):
|
||||||
setattr(args, self.dest, values)
|
setattr(args, self.dest, values)
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool")
|
parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool")
|
||||||
parser.add_argument("url", metavar="ws_url",
|
parser.add_argument("url", metavar="ws_url",
|
||||||
help="websocket url. ex. ws://echo.websocket.events/")
|
help="websocket url. ex. ws://echo.websocket.events/")
|
||||||
|
@ -93,7 +93,7 @@ def parse_args():
|
||||||
|
|
||||||
class RawInput:
|
class RawInput:
|
||||||
|
|
||||||
def raw_input(self, prompt):
|
def raw_input(self, prompt: str = "") -> str:
|
||||||
line = input(prompt)
|
line = input(prompt)
|
||||||
|
|
||||||
if ENCODING and ENCODING != "utf-8" and not isinstance(line, str):
|
if ENCODING and ENCODING != "utf-8" and not isinstance(line, str):
|
||||||
|
@ -106,29 +106,29 @@ class RawInput:
|
||||||
|
|
||||||
class InteractiveConsole(RawInput, code.InteractiveConsole):
|
class InteractiveConsole(RawInput, code.InteractiveConsole):
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data: str) -> None:
|
||||||
sys.stdout.write("\033[2K\033[E")
|
sys.stdout.write("\033[2K\033[E")
|
||||||
# sys.stdout.write("\n")
|
# sys.stdout.write("\n")
|
||||||
sys.stdout.write("\033[34m< " + data + "\033[39m")
|
sys.stdout.write("\033[34m< " + data + "\033[39m")
|
||||||
sys.stdout.write("\n> ")
|
sys.stdout.write("\n> ")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> str:
|
||||||
return self.raw_input("> ")
|
return self.raw_input("> ")
|
||||||
|
|
||||||
|
|
||||||
class NonInteractive(RawInput):
|
class NonInteractive(RawInput):
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data: str) -> None:
|
||||||
sys.stdout.write(data)
|
sys.stdout.write(data)
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> str:
|
||||||
return self.raw_input("")
|
return self.raw_input("")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
if args.verbose > 1:
|
if args.verbose > 1:
|
||||||
|
@ -154,25 +154,25 @@ def main():
|
||||||
console = InteractiveConsole()
|
console = InteractiveConsole()
|
||||||
print("Press Ctrl+C to quit")
|
print("Press Ctrl+C to quit")
|
||||||
|
|
||||||
def recv():
|
def recv() -> tuple:
|
||||||
try:
|
try:
|
||||||
frame = ws.recv_frame()
|
frame = ws.recv_frame()
|
||||||
except websocket.WebSocketException:
|
except websocket.WebSocketException:
|
||||||
return websocket.ABNF.OPCODE_CLOSE, None
|
return websocket.ABNF.OPCODE_CLOSE, ""
|
||||||
if not frame:
|
if not frame:
|
||||||
raise websocket.WebSocketException("Not a valid frame %s" % frame)
|
raise websocket.WebSocketException("Not a valid frame {frame}".format(frame=frame))
|
||||||
elif frame.opcode in OPCODE_DATA:
|
elif frame.opcode in OPCODE_DATA:
|
||||||
return frame.opcode, frame.data
|
return frame.opcode, frame.data
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
|
elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
|
||||||
ws.send_close()
|
ws.send_close()
|
||||||
return frame.opcode, None
|
return frame.opcode, ""
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_PING:
|
elif frame.opcode == websocket.ABNF.OPCODE_PING:
|
||||||
ws.pong(frame.data)
|
ws.pong(frame.data)
|
||||||
return frame.opcode, frame.data
|
return frame.opcode, frame.data
|
||||||
|
|
||||||
return frame.opcode, frame.data
|
return frame.opcode, frame.data
|
||||||
|
|
||||||
def recv_ws():
|
def recv_ws() -> None:
|
||||||
while True:
|
while True:
|
||||||
opcode, data = recv()
|
opcode, data = recv()
|
||||||
msg = None
|
msg = None
|
||||||
|
@ -193,7 +193,7 @@ def main():
|
||||||
data = repr(data)
|
data = repr(data)
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
msg = "%s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data)
|
msg = "{opcode}: {data}".format(opcode=websocket.ABNF.OPCODE_MAP.get(opcode), data=data)
|
||||||
else:
|
else:
|
||||||
msg = data
|
msg = data
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import asyncio
|
||||||
import websockets
|
import websockets
|
||||||
import os
|
import os
|
||||||
|
|
||||||
LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '8765')
|
LOCAL_WS_SERVER_PORT = int(os.environ.get('LOCAL_WS_SERVER_PORT', '8765'))
|
||||||
|
|
||||||
|
|
||||||
async def echo(websocket, path):
|
async def echo(websocket, path):
|
||||||
|
|
|
@ -8,7 +8,7 @@ import unittest
|
||||||
test_abnf.py
|
test_abnf.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import unittest
|
||||||
test_app.py
|
test_app.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -112,25 +112,6 @@ class WebSocketAppTest(unittest.TestCase):
|
||||||
teardown = app.run_forever()
|
teardown = app.run_forever()
|
||||||
self.assertEqual(teardown, False)
|
self.assertEqual(teardown, False)
|
||||||
|
|
||||||
@unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
|
|
||||||
def testRunForeverTeardownExceptionalExit(self):
|
|
||||||
""" The WebSocketApp.run_forever() method should return `True` when the application ends with an exception.
|
|
||||||
It should also invoke the `on_error` callback before exiting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def break_it():
|
|
||||||
# Deliberately break the WebSocketApp by closing the inner socket.
|
|
||||||
app.sock.close()
|
|
||||||
|
|
||||||
def on_error(_, err):
|
|
||||||
WebSocketAppTest.on_error_data = str(err)
|
|
||||||
|
|
||||||
app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_error=on_error)
|
|
||||||
threading.Timer(interval=0.2, function=break_it).start()
|
|
||||||
teardown = app.run_forever(ping_timeout=0.1)
|
|
||||||
self.assertEqual(teardown, True)
|
|
||||||
self.assertTrue(len(WebSocketAppTest.on_error_data) > 0)
|
|
||||||
|
|
||||||
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
|
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
|
||||||
def testSockMaskKey(self):
|
def testSockMaskKey(self):
|
||||||
""" A WebSocketApp should forward the received mask_key function down
|
""" A WebSocketApp should forward the received mask_key function down
|
||||||
|
@ -310,8 +291,8 @@ class WebSocketAppTest(unittest.TestCase):
|
||||||
app.run_forever(ping_interval=2, ping_timeout=1, reconnect=3)
|
app.run_forever(ping_interval=2, ping_timeout=1, reconnect=3)
|
||||||
|
|
||||||
self.assertEqual(pong_count, 2)
|
self.assertEqual(pong_count, 2)
|
||||||
self.assertIsInstance(exc, ValueError)
|
self.assertIsInstance(exc, ws.WebSocketTimeoutException)
|
||||||
self.assertEqual(str(exc), "Invalid file object: None")
|
self.assertEqual(str(exc), "ping/pong timed out")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -5,7 +5,7 @@ from websocket._cookiejar import SimpleCookieJar
|
||||||
test_cookiejar.py
|
test_cookiejar.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -13,7 +13,7 @@ import socket
|
||||||
test_http.py
|
test_http.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -8,7 +8,7 @@ from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _i
|
||||||
test_url.py
|
test_url.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -254,6 +254,24 @@ class ProxyInfoTest(unittest.TestCase):
|
||||||
os.environ["https_proxy"] = "http://localhost2:3128/"
|
os.environ["https_proxy"] = "http://localhost2:3128/"
|
||||||
self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None))
|
self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None))
|
||||||
|
|
||||||
|
os.environ["http_proxy"] = ""
|
||||||
|
os.environ["https_proxy"] = "http://localhost2/"
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, None))
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None))
|
||||||
|
os.environ["http_proxy"] = ""
|
||||||
|
os.environ["https_proxy"] = "http://localhost2:3128/"
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None))
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None))
|
||||||
|
|
||||||
|
os.environ["http_proxy"] = "http://localhost/"
|
||||||
|
os.environ["https_proxy"] = ""
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None))
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None))
|
||||||
|
os.environ["http_proxy"] = "http://localhost:3128/"
|
||||||
|
os.environ["https_proxy"] = ""
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None))
|
||||||
|
self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None))
|
||||||
|
|
||||||
os.environ["http_proxy"] = "http://a:b@localhost/"
|
os.environ["http_proxy"] = "http://a:b@localhost/"
|
||||||
self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, ("a", "b")))
|
self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, ("a", "b")))
|
||||||
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
|
os.environ["http_proxy"] = "http://a:b@localhost:3128/"
|
||||||
|
|
|
@ -15,7 +15,7 @@ from base64 import decodebytes as base64decode
|
||||||
test_websocket.py
|
test_websocket.py
|
||||||
websocket - WebSocket client library for Python
|
websocket - WebSocket client library for Python
|
||||||
|
|
||||||
Copyright 2022 engn33r
|
Copyright 2023 engn33r
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -47,7 +47,7 @@ tzdata==2023.3
|
||||||
tzlocal==4.2
|
tzlocal==4.2
|
||||||
urllib3==2.0.4
|
urllib3==2.0.4
|
||||||
webencodings==0.5.1
|
webencodings==0.5.1
|
||||||
websocket-client==1.5.1
|
websocket-client==1.6.2
|
||||||
xmltodict==0.13.0
|
xmltodict==0.13.0
|
||||||
zipp==3.15.0
|
zipp==3.15.0
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue