diff --git a/lib/websocket/__init__.py b/lib/websocket/__init__.py index 05aae2bd..a5a39502 100644 --- a/lib/websocket/__init__.py +++ b/lib/websocket/__init__.py @@ -2,7 +2,7 @@ __init__.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (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 ._socket import * -__version__ = "1.2.3" +__version__ = "1.3.2" diff --git a/lib/websocket/_abnf.py b/lib/websocket/_abnf.py index e9909ff6..2e5ad97c 100644 --- a/lib/websocket/_abnf.py +++ b/lib/websocket/_abnf.py @@ -1,12 +1,17 @@ -""" +import array +import os +import struct +import sys -""" +from ._exceptions import * +from ._utils import validate_utf8 +from threading import Lock """ _abnf.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,14 +25,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import array -import os -import struct -import sys - -from ._exceptions import * -from ._utils import validate_utf8 -from threading import Lock try: # If wsaccel is available, use compiled routines to mask data. @@ -79,6 +76,8 @@ STATUS_POLICY_VIOLATION = 1008 STATUS_MESSAGE_TOO_BIG = 1009 STATUS_INVALID_EXTENSION = 1010 STATUS_UNEXPECTED_CONDITION = 1011 +STATUS_SERVICE_RESTART = 1012 +STATUS_TRY_AGAIN_LATER = 1013 STATUS_BAD_GATEWAY = 1014 STATUS_TLS_HANDSHAKE_ERROR = 1015 @@ -92,6 +91,8 @@ VALID_CLOSE_STATUS = ( STATUS_MESSAGE_TOO_BIG, STATUS_INVALID_EXTENSION, STATUS_UNEXPECTED_CONDITION, + STATUS_SERVICE_RESTART, + STATUS_TRY_AGAIN_LATER, STATUS_BAD_GATEWAY, ) @@ -146,7 +147,7 @@ class ABNF: self.data = data self.get_mask_key = os.urandom - def validate(self, skip_utf8_validation=False): + def validate(self, skip_utf8_validation=False) -> None: """ Validate the ABNF frame. @@ -174,13 +175,13 @@ class ABNF: code = 256 * self.data[0] + self.data[1] if not self._is_valid_close_status(code): - raise WebSocketProtocolException("Invalid close opcode.") + raise WebSocketProtocolException("Invalid close opcode %r", code) @staticmethod - def _is_valid_close_status(code): + def _is_valid_close_status(code: int) -> bool: return code in VALID_CLOSE_STATUS or (3000 <= code < 5000) - def __str__(self): + def __str__(self) -> str: return "fin=" + str(self.fin) \ + " opcode=" + str(self.opcode) \ + " data=" + str(self.data) @@ -206,7 +207,7 @@ class ABNF: # mask must be set if send data from client return ABNF(fin, 0, 0, 0, opcode, 1, data) - def format(self): + def format(self) -> bytes: """ Format this object to string(byte array) to send data to server. """ @@ -251,9 +252,9 @@ class ABNF: Parameters ---------- - mask_key: - 4 byte string. - data: + mask_key: bytes or str + 4 byte mask. + data: bytes or str data to mask/unmask. """ if data is None: @@ -286,7 +287,7 @@ class frame_buffer: self.length = None self.mask = None - def has_received_header(self): + def has_received_header(self) -> bool: return self.header is None def recv_header(self): @@ -308,7 +309,7 @@ class frame_buffer: return False return self.header[frame_buffer._HEADER_MASK_INDEX] - def has_received_length(self): + def has_received_length(self) -> bool: return self.length is None def recv_length(self): @@ -323,7 +324,7 @@ class frame_buffer: else: self.length = length_bits - def has_received_mask(self): + def has_received_mask(self) -> bool: return self.mask is None def recv_mask(self): @@ -360,7 +361,7 @@ class frame_buffer: return frame - def recv_strict(self, bufsize): + def recv_strict(self, bufsize: int) -> bytes: shortage = bufsize - sum(map(len, self.recv_buffer)) while shortage > 0: # Limit buffer size that we pass to socket.recv() to avoid diff --git a/lib/websocket/_app.py b/lib/websocket/_app.py index 1afd3d20..da49ec78 100644 --- a/lib/websocket/_app.py +++ b/lib/websocket/_app.py @@ -1,12 +1,18 @@ -""" - -""" +import selectors +import sys +import threading +import time +import traceback +from ._abnf import ABNF +from ._core import WebSocket, getdefaulttimeout +from ._exceptions import * +from . import _logging """ _app.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,16 +26,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import selectors -import sys -import threading -import time -import traceback -from ._abnf import ABNF -from ._core import WebSocket, getdefaulttimeout -from ._exceptions import * -from . import _logging - __all__ = ["WebSocketApp"] @@ -86,6 +82,20 @@ class SSLDispatcher: return r[0][0] +class WrappedDispatcher: + """ + WrappedDispatcher + """ + def __init__(self, app, ping_timeout, dispatcher): + self.app = app + self.ping_timeout = ping_timeout + self.dispatcher = dispatcher + + def read(self, sock, read_callback, check_callback): + self.dispatcher.read(sock, read_callback) + self.ping_timeout and self.dispatcher.timeout(self.ping_timeout, check_callback) + + class WebSocketApp: """ Higher level of APIs are provided. The interface is like JavaScript WebSocket object. @@ -97,7 +107,8 @@ class WebSocketApp: on_cont_message=None, keep_running=True, get_mask_key=None, cookie=None, subprotocols=None, - on_data=None): + on_data=None, + socket=None): """ WebSocketApp initialization @@ -153,6 +164,8 @@ class WebSocketApp: Cookie value. subprotocols: list List of available sub protocols. Default is None. + socket: socket + Pre-initialized stream socket. """ self.url = url self.header = header if header is not None else [] @@ -172,6 +185,7 @@ class WebSocketApp: self.last_ping_tm = 0 self.last_pong_tm = 0 self.subprotocols = subprotocols + self.prepared_socket = socket def send(self, data, opcode=ABNF.OPCODE_TEXT): """ @@ -258,7 +272,8 @@ class WebSocketApp: Returns ------- teardown: bool - False if caught KeyboardInterrupt, True if other exception was raised during a loop + False if the `WebSocketApp` is closed or caught KeyboardInterrupt, + True if any other exception was raised during a loop. """ if ping_timeout is not None and ping_timeout <= 0: @@ -315,9 +330,8 @@ class WebSocketApp: http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols, host=host, origin=origin, suppress_origin=suppress_origin, - proxy_type=proxy_type) - if not dispatcher: - dispatcher = self.create_dispatcher(ping_timeout) + proxy_type=proxy_type, socket=self.prepared_socket) + dispatcher = self.create_dispatcher(ping_timeout, dispatcher) self._callback(self.on_open) @@ -367,6 +381,7 @@ class WebSocketApp: return True dispatcher.read(self.sock.sock, read, check) + return False except (Exception, KeyboardInterrupt, SystemExit) as e: self._callback(self.on_error, e) if isinstance(e, SystemExit): @@ -375,7 +390,9 @@ class WebSocketApp: teardown() return not isinstance(e, KeyboardInterrupt) - def create_dispatcher(self, ping_timeout): + def create_dispatcher(self, ping_timeout, dispatcher=None): + if dispatcher: # If custom dispatcher is set, use WrappedDispatcher + return WrappedDispatcher(self, ping_timeout, dispatcher) timeout = ping_timeout or 10 if self.sock.is_ssl(): return SSLDispatcher(self, timeout) diff --git a/lib/websocket/_cookiejar.py b/lib/websocket/_cookiejar.py index 87853834..5476d1d4 100644 --- a/lib/websocket/_cookiejar.py +++ b/lib/websocket/_cookiejar.py @@ -1,12 +1,10 @@ -""" - -""" +import http.cookies """ _cookiejar.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +18,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import http.cookies class SimpleCookieJar: diff --git a/lib/websocket/_core.py b/lib/websocket/_core.py index e26c8b11..c36b7800 100644 --- a/lib/websocket/_core.py +++ b/lib/websocket/_core.py @@ -1,27 +1,3 @@ -""" -_core.py -==================================== -WebSocket Python client -""" - -""" -_core.py -websocket - WebSocket client library for Python - -Copyright 2021 engn33r - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" import socket import struct import threading @@ -37,6 +13,25 @@ from ._socket import * from ._ssl_compat import * from ._utils import * +""" +_core.py +websocket - WebSocket client library for Python + +Copyright 2022 engn33r + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + __all__ = ['WebSocket', 'create_connection'] @@ -250,14 +245,14 @@ class WebSocket: options.pop('socket', None)) try: - self.handshake_response = handshake(self.sock, *addrs, **options) + self.handshake_response = handshake(self.sock, url, *addrs, **options) for attempt in range(options.pop('redirect_limit', 3)): if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: url = self.handshake_response.headers['location'] self.sock.close() self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), options.pop('socket', None)) - self.handshake_response = handshake(self.sock, *addrs, **options) + self.handshake_response = handshake(self.sock, url, *addrs, **options) self.connected = True except: if self.sock: diff --git a/lib/websocket/_exceptions.py b/lib/websocket/_exceptions.py index b92b1f40..811d5945 100644 --- a/lib/websocket/_exceptions.py +++ b/lib/websocket/_exceptions.py @@ -1,12 +1,8 @@ -""" -Define WebSocket exceptions -""" - """ _exceptions.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/lib/websocket/_handshake.py b/lib/websocket/_handshake.py index f9dabb57..f032c4b5 100644 --- a/lib/websocket/_handshake.py +++ b/lib/websocket/_handshake.py @@ -2,7 +2,7 @@ _handshake.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -47,8 +47,8 @@ class handshake_response: CookieJar.add(headers.get("set-cookie")) -def handshake(sock, hostname, port, resource, **options): - headers, key = _get_handshake_headers(resource, hostname, port, options) +def handshake(sock, url, hostname, port, resource, **options): + headers, key = _get_handshake_headers(resource, url, hostname, port, options) header_str = "\r\n".join(headers) send(sock, header_str) @@ -72,7 +72,7 @@ def _pack_hostname(hostname): return hostname -def _get_handshake_headers(resource, host, port, options): +def _get_handshake_headers(resource, url, host, port, options): headers = [ "GET %s HTTP/1.1" % resource, "Upgrade: websocket" @@ -86,9 +86,14 @@ def _get_handshake_headers(resource, host, port, options): else: headers.append("Host: %s" % hostport) + # 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 + scheme, url = url.split(":", 1) if "suppress_origin" not in options or not options["suppress_origin"]: if "origin" in options and options["origin"] is not None: headers.append("Origin: %s" % options["origin"]) + elif scheme == "wss": + headers.append("Origin: https://%s" % hostport) else: headers.append("Origin: http://%s" % hostport) diff --git a/lib/websocket/_http.py b/lib/websocket/_http.py index 603fa00f..04f32f5a 100644 --- a/lib/websocket/_http.py +++ b/lib/websocket/_http.py @@ -2,7 +2,7 @@ _http.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -186,12 +186,10 @@ def _open_socket(addrinfo_list, sockopt, timeout): except socket.error as error: error.remote_ip = str(address[0]) try: - eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED) + eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED, errno.ENETUNREACH) except: - eConnRefused = (errno.ECONNREFUSED, ) - if error.errno == errno.EINTR: - continue - elif error.errno in eConnRefused: + eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH) + if error.errno in eConnRefused: err = error continue else: diff --git a/lib/websocket/_logging.py b/lib/websocket/_logging.py index 480d43b0..df690dcc 100644 --- a/lib/websocket/_logging.py +++ b/lib/websocket/_logging.py @@ -1,12 +1,10 @@ -""" - -""" +import logging """ _logging.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +18,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import logging _logger = logging.getLogger('websocket') try: diff --git a/lib/websocket/_socket.py b/lib/websocket/_socket.py index 4d9cc097..54e63997 100644 --- a/lib/websocket/_socket.py +++ b/lib/websocket/_socket.py @@ -1,12 +1,16 @@ -""" +import errno +import selectors +import socket -""" +from ._exceptions import * +from ._ssl_compat import * +from ._utils import * """ _socket.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +24,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import errno -import selectors -import socket - -from ._exceptions import * -from ._ssl_compat import * -from ._utils import * DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)] if hasattr(socket, "SO_KEEPALIVE"): @@ -92,9 +89,7 @@ def recv(sock, bufsize): pass except socket.error as exc: error_code = extract_error_code(exc) - if error_code is None: - raise - if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: + if error_code != errno.EAGAIN and error_code != errno.EWOULDBLOCK: raise sel = selectors.DefaultSelector() @@ -111,6 +106,8 @@ def recv(sock, bufsize): bytes_ = sock.recv(bufsize) else: bytes_ = _recv() + except TimeoutError: + raise WebSocketTimeoutException("Connection timed out") except socket.timeout as e: message = extract_err_message(e) raise WebSocketTimeoutException(message) diff --git a/lib/websocket/_ssl_compat.py b/lib/websocket/_ssl_compat.py index f4af524e..e2278401 100644 --- a/lib/websocket/_ssl_compat.py +++ b/lib/websocket/_ssl_compat.py @@ -2,7 +2,7 @@ _ssl_compat.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/lib/websocket/_url.py b/lib/websocket/_url.py index f2a55019..2d3d2653 100644 --- a/lib/websocket/_url.py +++ b/lib/websocket/_url.py @@ -1,11 +1,14 @@ -""" +import os +import socket +import struct + +from urllib.parse import unquote, urlparse -""" """ _url.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,13 +23,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os -import socket -import struct - -from urllib.parse import unquote, urlparse - - __all__ = ["parse_url", "get_proxy_info"] diff --git a/lib/websocket/_utils.py b/lib/websocket/_utils.py index 21fc437c..fdcf345b 100644 --- a/lib/websocket/_utils.py +++ b/lib/websocket/_utils.py @@ -2,7 +2,7 @@ _url.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/lib/websocket/_wsdump.py b/lib/websocket/_wsdump.py index 4d15f413..860ac342 100644 --- a/lib/websocket/_wsdump.py +++ b/lib/websocket/_wsdump.py @@ -4,7 +4,7 @@ wsdump.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ class VAction(argparse.Action): def parse_args(): parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool") parser.add_argument("url", metavar="ws_url", - help="websocket url. ex. ws://echo.websocket.org/") + help="websocket url. ex. ws://echo.websocket.events/") parser.add_argument("-p", "--proxy", help="proxy url. ex. http://127.0.0.1:8080") parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction, diff --git a/lib/websocket/tests/data/header03.txt b/lib/websocket/tests/data/header03.txt index 030e13a8..1a81dc70 100644 --- a/lib/websocket/tests/data/header03.txt +++ b/lib/websocket/tests/data/header03.txt @@ -3,5 +3,6 @@ Connection: Upgrade, Keep-Alive Upgrade: WebSocket Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= Set-Cookie: Token=ABCDE +Set-Cookie: Token=FGHIJ some_header: something diff --git a/lib/websocket/tests/test_abnf.py b/lib/websocket/tests/test_abnf.py index 7f156dc9..7c9d89d8 100644 --- a/lib/websocket/tests/test_abnf.py +++ b/lib/websocket/tests/test_abnf.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- # +import websocket as ws +from websocket._abnf import * +import unittest + """ test_abnf.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +23,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import websocket as ws -from websocket._abnf import * -import unittest - class ABNFTest(unittest.TestCase): diff --git a/lib/websocket/tests/test_app.py b/lib/websocket/tests/test_app.py index cd1146b3..ac2a7dd5 100644 --- a/lib/websocket/tests/test_app.py +++ b/lib/websocket/tests/test_app.py @@ -1,10 +1,17 @@ # -*- coding: utf-8 -*- # +import os +import os.path +import threading +import websocket as ws +import ssl +import unittest + """ test_app.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,12 +26,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os -import os.path -import websocket as ws -import ssl -import unittest - # Skip test to access the internet unless TEST_WITH_INTERNET == 1 TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' # Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 @@ -45,11 +46,13 @@ class WebSocketAppTest(unittest.TestCase): WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + WebSocketAppTest.on_error_data = WebSocketAppTest.NotSetYet() def tearDown(self): WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + WebSocketAppTest.on_error_data = WebSocketAppTest.NotSetYet() @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") def testKeepRunning(self): @@ -77,6 +80,54 @@ class WebSocketAppTest(unittest.TestCase): app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_close=on_close, on_message=on_message) app.run_forever() + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + def testRunForeverDispatcher(self): + """ A WebSocketApp should keep running as long as its self.keep_running + is not False (in the boolean context). + """ + + def on_open(self, *args, **kwargs): + """ Send a message, receive, and send one more + """ + self.send("hello!") + self.recv() + self.send("goodbye!") + + def on_message(wsapp, message): + print(message) + self.close() + + app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_message=on_message) + app.run_forever(dispatcher="Dispatcher") + + @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + def testRunForeverTeardownCleanExit(self): + """ The WebSocketApp.run_forever() method should return `False` when the application ends gracefully. + """ + app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT) + threading.Timer(interval=0.2, function=app.close).start() + teardown = app.run_forever() + 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") def testSockMaskKey(self): """ A WebSocketApp should forward the received mask_key function down @@ -86,7 +137,7 @@ class WebSocketAppTest(unittest.TestCase): def my_mask_key_func(): return "\x00\x00\x00\x00" - app = ws.WebSocketApp('wss://stream.meetup.com/2/rsvps', get_mask_key=my_mask_key_func) + app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', get_mask_key=my_mask_key_func) # if numpy is installed, this assertion fail # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. @@ -136,7 +187,7 @@ class WebSocketAppTest(unittest.TestCase): def testOpcodeBinary(self): """ Test WebSocketApp binary opcode """ - + # The lack of wss:// in the URL below is on purpose app = ws.WebSocketApp('streaming.vn.teslamotors.com/streaming/') app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload") diff --git a/lib/websocket/tests/test_cookiejar.py b/lib/websocket/tests/test_cookiejar.py index 5bf1fcae..559b2e00 100644 --- a/lib/websocket/tests/test_cookiejar.py +++ b/lib/websocket/tests/test_cookiejar.py @@ -1,12 +1,11 @@ -""" - -""" +import unittest +from websocket._cookiejar import SimpleCookieJar """ test_cookiejar.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,8 +19,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import unittest -from websocket._cookiejar import SimpleCookieJar class CookieJarTest(unittest.TestCase): diff --git a/lib/websocket/tests/test_http.py b/lib/websocket/tests/test_http.py index fda467d7..649e0fe6 100644 --- a/lib/websocket/tests/test_http.py +++ b/lib/websocket/tests/test_http.py @@ -1,10 +1,19 @@ # -*- coding: utf-8 -*- # +import os +import os.path +import websocket as ws +from websocket._http import proxy_info, read_headers, _start_proxied_socket, _tunnel, _get_addrinfo_list, connect +import unittest +import ssl +import websocket +import socket + """ test_http.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,15 +28,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os -import os.path -import websocket as ws -from websocket._http import proxy_info, read_headers, _start_proxied_socket, _tunnel, _get_addrinfo_list, connect -import unittest -import ssl -import websocket -import socket - try: from python_socks._errors import ProxyError, ProxyTimeoutError, ProxyConnectionError except: diff --git a/lib/websocket/tests/test_url.py b/lib/websocket/tests/test_url.py index ad3a3b1b..7e155fd1 100644 --- a/lib/websocket/tests/test_url.py +++ b/lib/websocket/tests/test_url.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- # +import os +import unittest +from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host + """ test_url.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +23,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os -import unittest -from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host - class UrlTest(unittest.TestCase): @@ -209,73 +209,73 @@ class ProxyInfoTest(unittest.TestCase): del os.environ["no_proxy"] def testProxyFromArgs(self): - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), + self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost"), ("localhost", 0, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), + self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost"), ("localhost", 0, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")), + self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_auth=("a", "b")), ("localhost", 0, ("a", "b"))) self.assertEqual( - get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), + get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")), + self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_auth=("a", "b")), ("localhost", 0, ("a", "b"))) self.assertEqual( - get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), + get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, + self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, no_proxy=["example.com"], proxy_auth=("a", "b")), ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, - no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")), + self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, + no_proxy=["echo.websocket.events"], proxy_auth=("a", "b")), (None, 0, None)) def testProxyFromEnv(self): os.environ["http_proxy"] = "http://localhost/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None)) os.environ["http_proxy"] = "http://localhost:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None)) os.environ["http_proxy"] = "http://localhost/" os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None)) os.environ["http_proxy"] = "http://localhost:3128/" os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None)) os.environ["http_proxy"] = "http://localhost/" os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, None)) os.environ["http_proxy"] = "http://localhost:3128/" os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None)) + self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None)) os.environ["http_proxy"] = "http://a:b@localhost/" - self.assertEqual(get_proxy_info("echo.websocket.org", 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/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, ("a", "b"))) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", 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["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, ("a", "b"))) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, ("a", "b"))) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, ("a", "b"))) os.environ["http_proxy"] = "http://john%40example.com:P%40SSWORD@localhost:3128/" os.environ["https_proxy"] = "http://john%40example.com:P%40SSWORD@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("john@example.com", "P@SSWORD"))) + self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, ("john@example.com", "P@SSWORD"))) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" @@ -283,12 +283,12 @@ class ProxyInfoTest(unittest.TestCase): self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b"))) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org" - self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) + os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.events" + self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None)) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - os.environ["no_proxy"] = "example1.com,example2.com, .websocket.org" - self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) + os.environ["no_proxy"] = "example1.com,example2.com, .websocket.events" + self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None)) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" diff --git a/lib/websocket/tests/test_websocket.py b/lib/websocket/tests/test_websocket.py index 8b34aa51..ae42ab54 100644 --- a/lib/websocket/tests/test_websocket.py +++ b/lib/websocket/tests/test_websocket.py @@ -1,14 +1,21 @@ # -*- coding: utf-8 -*- # -""" - -""" +import os +import os.path +import socket +import websocket as ws +import unittest +from websocket._handshake import _create_sec_websocket_key, \ + _validate as _validate_header +from websocket._http import read_headers +from websocket._utils import validate_utf8 +from base64 import decodebytes as base64decode """ test_websocket.py websocket - WebSocket client library for Python -Copyright 2021 engn33r +Copyright 2022 engn33r Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,18 +30,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os -import os.path -import socket -import websocket as ws -from websocket._handshake import _create_sec_websocket_key, \ - _validate as _validate_header -from websocket._http import read_headers -from websocket._utils import validate_utf8 -from base64 import decodebytes as base64decode - -import unittest - try: import ssl from ssl import SSLError @@ -201,14 +196,16 @@ class WebSocketTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testIter(self): count = 2 - for _ in ws.create_connection('wss://stream.meetup.com/2/rsvps'): + s = ws.create_connection('wss://api.bitfinex.com/ws/2') + s.send('{"event": "subscribe", "channel": "ticker"}') + for _ in s: count -= 1 if count == 0: break @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testNext(self): - sock = ws.create_connection('wss://stream.meetup.com/2/rsvps') + sock = ws.create_connection('wss://api.bitfinex.com/ws/2') self.assertEqual(str, type(next(sock))) def testInternalRecvStrict(self): @@ -383,6 +380,7 @@ class WebSocketTest(unittest.TestCase): s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, headers={"User-Agent": "PythonWebsocketClient"}) self.assertNotEqual(s, None) + self.assertEqual(s.getsubprotocol(), None) s.send("Hello, World") result = s.recv() self.assertEqual(result, "Hello, World") diff --git a/requirements.txt b/requirements.txt index 99f8033e..b91e719f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ tzdata==2022.1 tzlocal==4.2 urllib3==1.26.9 webencodings==0.5.1 -websocket-client==1.2.3 +websocket-client==1.3.2 xmltodict==0.12.0 zipp==3.8.0