New Upstream Snapshot - websocket-client
Ready changes
Summary
Merged new upstream version: 1.4.2+git20230113.1.601a902 (was: 1.2.3).
Resulting package
Built on 2023-01-19T14:08 (took 5m58s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots python3-websocket
Lintian Result
Diff
diff --git a/ChangeLog b/ChangeLog
index 6789489..409041a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,44 @@
ChangeLog
============
+- 1.4.2
+ - create_dispatcher is determined by URL ws/wss, NOT by presence of sslopt args, to maintain consistency (#875)
+ - Remove redundant key generation line (#864)
+ - Updated docs to fix old links and updated CI to include Python 3.11
+
+- 1.4.1
+ - Fix stack growth bug when `run_forever` reconnects (#854)
+ - Add doctest CI for sphinx docs code examples (d150099)
+ - General docs improvements
+
+- 1.4.0
+ - Fix automatic reconnect with `run_forever` (#838)
+ - Allow a timeout to be set when using a proxy (#842)
+
+- 1.3.3
+ - Fix unclosed socket error (#826)
+ - Update header dict access (#818)
+ - Add utf8 workaround to docs (fc9ee9f)
+
+- 1.3.2
+ - Add support for pre-initialized stream socket in new WebSocketApp (#804)
+ - Remove rel.saferead() in examples (f0bf03d)
+ - Increase scope of linting checks (dca4022)
+ - Start adding type hints (a8a4099)
+
+- 1.3.1
+ - Fix 10 year old bug and improve dispatcher handling for run_forever (#795)
+ - Fix run_forever to never return None, only return True or False, and add two tests (#788)
+ - Remove Python 3.6 support, EOL in Dec 2021
+
+- 1.3.0
+ - BREAKING: Set Origin header to use https:// scheme when wss:// WebSocket URL is passed (#787)
+ - Replace deprecated/broken WebSocket URLs with working ones (6ad5197)
+ - Add documentation referencing rel for automatic reconnection with run_forever()
+ - Add missing opcodes 1012, 1013 (#771)
+ - Add errno.ENETUNREACH to improve error handling (da1b050)
+ - Minor documentation improvements and typo fixes
+
- 1.2.3
- Fix broken run_forever() functionality (#769)
diff --git a/LICENSE b/LICENSE
index 27269dd..a5db00e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- 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/PKG-INFO b/PKG-INFO
index 116110c..b0b7e1d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,35 +1,34 @@
Metadata-Version: 2.1
Name: websocket-client
-Version: 1.2.3
+Version: 1.4.2
Summary: WebSocket client for Python with low level API options
Home-page: https://github.com/websocket-client/websocket-client.git
+Download-URL: https://github.com/websocket-client/websocket-client/releases
Author: liris
Author-email: liris.pp@gmail.com
License: Apache-2.0
-Download-URL: https://github.com/websocket-client/websocket-client/releases
Project-URL: Documentation, https://websocket-client.readthedocs.io/
Project-URL: Source, https://github.com/websocket-client/websocket-client/
Keywords: websockets client
-Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Intended Audience :: Developers
-Requires-Python: >=3.6
+Requires-Python: >=3.7
Description-Content-Type: text/markdown
-Provides-Extra: test
-Provides-Extra: optional
Provides-Extra: docs
+Provides-Extra: optional
+Provides-Extra: test
License-File: LICENSE
[![docs](https://readthedocs.org/projects/websocket-client/badge/?style=flat)](https://websocket-client.readthedocs.io/)
@@ -59,7 +58,7 @@ Please see the [contribution guidelines](https://github.com/websocket-client/web
## Installation
You can use either `python3 setup.py install` or `pip3 install websocket-client`
-to install. This module is tested on Python 3.6+.
+to install. This module is tested on Python 3.7+.
There are several optional dependencies that can be installed to enable
specific websocket-client features.
@@ -70,6 +69,9 @@ specific websocket-client features.
- To install `Sphinx` and `sphinx_rtd_theme` to build project documentation, use:
`pip3 install websocket-client[docs]`
+While not a strict dependency, [rel](https://github.com/bubbleboy14/registeredeventlistener)
+is useful when using `run_forever` with automatic reconnect. Install rel with `pip3 install rel`.
+
Footnote: Some shells, such as zsh, require you to escape the `[` and `]` characters with a `\`.
## Usage Tips
@@ -102,13 +104,28 @@ Many more examples are found in the
### Long-lived Connection
Most real-world WebSockets situations involve longer-lived connections.
-The WebSocketApp `run_forever` loop automatically tries to reconnect when a
-connection is lost, and provides a variety of event-based connection controls.
+The WebSocketApp `run_forever` loop will automatically try to reconnect
+to an open WebSocket connection when a network
+connection is lost if it is provided with:
+
+- a `dispatcher` argument (async dispatcher like rel or pyevent)
+- a non-zero `reconnect` argument (delay between disconnection and attempted reconnection)
+
+`run_forever` provides a variety of event-based connection controls
+using callbacks like `on_message` and `on_error`.
+`run_forever` does not automatically reconnect if the server
+closes the WebSocket gracefully (returning
+[a standard websocket close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1)).
+Customizing behavior when the server closes
+the WebSocket should be handled in the `on_close` callback.
+This example uses [rel](https://github.com/bubbleboy14/registeredeventlistener)
+for the dispatcher to provide automatic reconnection.
```python
import websocket
import _thread
import time
+import rel
def on_message(ws, message):
print(message)
@@ -120,24 +137,19 @@ def on_close(ws, close_status_code, close_msg):
print("### closed ###")
def on_open(ws):
- def run(*args):
- for i in range(3):
- time.sleep(1)
- ws.send("Hello %d" % i)
- time.sleep(1)
- ws.close()
- print("thread terminating...")
- _thread.start_new_thread(run, ())
+ print("Opened connection")
if __name__ == "__main__":
websocket.enableTrace(True)
- ws = websocket.WebSocketApp("ws://echo.websocket.org/",
+ ws = websocket.WebSocketApp("wss://api.gemini.com/v1/marketdata/BTCUSD",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close)
- ws.run_forever()
+ ws.run_forever(dispatcher=rel, reconnect=5) # Set dispatcher to automatic reconnection, 5 second reconnect delay if connection closed unexpectedly
+ rel.signal(2, rel.abort) # Keyboard Interrupt
+ rel.dispatch()
```
### Short-lived Connection
@@ -148,7 +160,9 @@ server is running and responds properly to a specific request.
```python
from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/")
+
+ws = create_connection("ws://echo.websocket.events/")
+print(ws.recv())
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
@@ -157,18 +171,3 @@ result = ws.recv()
print("Received '%s'" % result)
ws.close()
```
-
-If you want to customize socket options, set sockopt, as seen below:
-
-```python
-from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/",
- sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY),))
-```
-
-### Acknowledgements
-
-Thanks to @battlemidget and @ralphbean for helping migrate this project to
-Python 3.
-
-
diff --git a/README.md b/README.md
index 04c2bd9..d5c9bc5 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Please see the [contribution guidelines](https://github.com/websocket-client/web
## Installation
You can use either `python3 setup.py install` or `pip3 install websocket-client`
-to install. This module is tested on Python 3.6+.
+to install. This module is tested on Python 3.7+.
There are several optional dependencies that can be installed to enable
specific websocket-client features.
@@ -36,6 +36,9 @@ specific websocket-client features.
- To install `Sphinx` and `sphinx_rtd_theme` to build project documentation, use:
`pip3 install websocket-client[docs]`
+While not a strict dependency, [rel](https://github.com/bubbleboy14/registeredeventlistener)
+is useful when using `run_forever` with automatic reconnect. Install rel with `pip3 install rel`.
+
Footnote: Some shells, such as zsh, require you to escape the `[` and `]` characters with a `\`.
## Usage Tips
@@ -68,13 +71,28 @@ Many more examples are found in the
### Long-lived Connection
Most real-world WebSockets situations involve longer-lived connections.
-The WebSocketApp `run_forever` loop automatically tries to reconnect when a
-connection is lost, and provides a variety of event-based connection controls.
+The WebSocketApp `run_forever` loop will automatically try to reconnect
+to an open WebSocket connection when a network
+connection is lost if it is provided with:
+
+- a `dispatcher` argument (async dispatcher like rel or pyevent)
+- a non-zero `reconnect` argument (delay between disconnection and attempted reconnection)
+
+`run_forever` provides a variety of event-based connection controls
+using callbacks like `on_message` and `on_error`.
+`run_forever` does not automatically reconnect if the server
+closes the WebSocket gracefully (returning
+[a standard websocket close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1)).
+Customizing behavior when the server closes
+the WebSocket should be handled in the `on_close` callback.
+This example uses [rel](https://github.com/bubbleboy14/registeredeventlistener)
+for the dispatcher to provide automatic reconnection.
```python
import websocket
import _thread
import time
+import rel
def on_message(ws, message):
print(message)
@@ -86,24 +104,19 @@ def on_close(ws, close_status_code, close_msg):
print("### closed ###")
def on_open(ws):
- def run(*args):
- for i in range(3):
- time.sleep(1)
- ws.send("Hello %d" % i)
- time.sleep(1)
- ws.close()
- print("thread terminating...")
- _thread.start_new_thread(run, ())
+ print("Opened connection")
if __name__ == "__main__":
websocket.enableTrace(True)
- ws = websocket.WebSocketApp("ws://echo.websocket.org/",
+ ws = websocket.WebSocketApp("wss://api.gemini.com/v1/marketdata/BTCUSD",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close)
- ws.run_forever()
+ ws.run_forever(dispatcher=rel, reconnect=5) # Set dispatcher to automatic reconnection, 5 second reconnect delay if connection closed unexpectedly
+ rel.signal(2, rel.abort) # Keyboard Interrupt
+ rel.dispatch()
```
### Short-lived Connection
@@ -114,7 +127,9 @@ server is running and responds properly to a specific request.
```python
from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/")
+
+ws = create_connection("ws://echo.websocket.events/")
+print(ws.recv())
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
@@ -123,16 +138,3 @@ result = ws.recv()
print("Received '%s'" % result)
ws.close()
```
-
-If you want to customize socket options, set sockopt, as seen below:
-
-```python
-from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/",
- sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY),))
-```
-
-### Acknowledgements
-
-Thanks to @battlemidget and @ralphbean for helping migrate this project to
-Python 3.
diff --git a/debian/changelog b/debian/changelog
index 71ec60d..d2dcfbb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+websocket-client (1.4.2+git20230113.1.601a902-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 19 Jan 2023 14:04:05 -0000
+
websocket-client (1.2.3-1) unstable; urgency=medium
* New upstream release.
diff --git a/examples/echo_client.py b/examples/echo_client.py
index d4fa3b7..359ec38 100644
--- a/examples/echo_client.py
+++ b/examples/echo_client.py
@@ -2,7 +2,8 @@ import websocket
if __name__ == "__main__":
websocket.enableTrace(True)
- ws = websocket.create_connection("ws://echo.websocket.org/")
+ ws = websocket.create_connection("ws://echo.websocket.events/")
+ ws.recv()
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
diff --git a/examples/echoapp_client.py b/examples/echoapp_client.py
index bf70731..0d2b2f5 100644
--- a/examples/echoapp_client.py
+++ b/examples/echoapp_client.py
@@ -35,7 +35,7 @@ def on_open(ws):
if __name__ == "__main__":
websocket.enableTrace(True)
if len(sys.argv) < 2:
- host = "ws://echo.websocket.org/"
+ host = "ws://echo.websocket.events/"
else:
host = sys.argv[1]
ws = websocket.WebSocketApp(host,
diff --git a/examples/rel_client.py b/examples/rel_client.py
new file mode 100644
index 0000000..9b9c7da
--- /dev/null
+++ b/examples/rel_client.py
@@ -0,0 +1,11 @@
+import websocket
+import rel
+
+addr = "wss://api.gemini.com/v1/marketdata/%s"
+
+if __name__ == "__main__":
+ for symbol in ["BTCUSD", "ETHUSD", "ETHBTC"]:
+ ws = websocket.WebSocketApp(addr % (symbol,), on_message=lambda w, m: print(m))
+ ws.run_forever(dispatcher=rel)
+ rel.signal(2, rel.abort) # Keyboard Interrupt
+ rel.dispatch()
diff --git a/setup.py b/setup.py
index 6e034b0..cb14d1c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,12 @@
-"""
-
-"""
+import sys
+import pkg_resources
+from setuptools import setup, find_packages
"""
setup.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,12 +20,8 @@ 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 sys
-
-from setuptools import setup, find_packages
-import pkg_resources
-VERSION = "1.2.3"
+VERSION = "1.4.2"
install_requires = []
tests_require = []
@@ -41,7 +37,7 @@ setup(
license="Apache-2.0",
url="https://github.com/websocket-client/websocket-client.git",
download_url='https://github.com/websocket-client/websocket-client/releases',
- python_requires='>=3.6',
+ python_requires='>=3.7',
extras_require={
"test": ["websockets"],
"optional": ["python-socks", "wsaccel"],
@@ -51,11 +47,11 @@ setup(
"Development Status :: 4 - Beta",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Operating System :: Microsoft :: Windows",
diff --git a/websocket/__init__.py b/websocket/__init__.py
index 05aae2b..588a8f2 100644
--- a/websocket/__init__.py
+++ b/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.
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from ._abnf import *
-from ._app import WebSocketApp
+from ._app import WebSocketApp, setReconnect
from ._core import *
from ._exceptions import *
from ._logging import *
from ._socket import *
-__version__ = "1.2.3"
+__version__ = "1.4.2"
diff --git a/websocket/_abnf.py b/websocket/_abnf.py
index e9909ff..2e5ad97 100644
--- a/websocket/_abnf.py
+++ b/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: <type>
- 4 byte string.
- data: <type>
+ 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/websocket/_app.py b/websocket/_app.py
index 1afd3d2..e5a4484 100644
--- a/websocket/_app.py
+++ b/websocket/_app.py
@@ -1,12 +1,20 @@
-"""
-
-"""
+import inspect
+import selectors
+import sys
+import threading
+import time
+import traceback
+from ._abnf import ABNF
+from ._url import parse_url
+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,28 +28,43 @@ 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"]
+RECONNECT = 0
+
-class Dispatcher:
+def setReconnect(reconnectInterval):
+ global RECONNECT
+ RECONNECT = reconnectInterval
+
+
+class DispatcherBase:
"""
- Dispatcher
+ DispatcherBase
"""
def __init__(self, app, ping_timeout):
self.app = app
self.ping_timeout = ping_timeout
+ def timeout(self, seconds, callback):
+ time.sleep(seconds)
+ callback()
+
+ def reconnect(self, seconds, reconnector):
+ try:
+ while True:
+ _logging.info("reconnect() - retrying in %s seconds [%s frames in stack]" % (seconds, len(inspect.stack())))
+ time.sleep(seconds)
+ reconnector(reconnecting=True)
+ except KeyboardInterrupt as e:
+ _logging.info("User exited %s" % (e,))
+
+
+class Dispatcher(DispatcherBase):
+ """
+ Dispatcher
+ """
def read(self, sock, read_callback, check_callback):
while self.app.keep_running:
sel = selectors.DefaultSelector()
@@ -55,14 +78,10 @@ class Dispatcher:
sel.close()
-class SSLDispatcher:
+class SSLDispatcher(DispatcherBase):
"""
SSLDispatcher
"""
- def __init__(self, app, ping_timeout):
- self.app = app
- self.ping_timeout = ping_timeout
-
def read(self, sock, read_callback, check_callback):
while self.app.keep_running:
r = self.select()
@@ -86,6 +105,27 @@ 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
+ dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
+
+ def read(self, sock, read_callback, check_callback):
+ self.dispatcher.read(sock, read_callback)
+ self.ping_timeout and self.timeout(self.ping_timeout, check_callback)
+
+ def timeout(self, seconds, callback):
+ self.dispatcher.timeout(seconds, callback)
+
+ def reconnect(self, seconds, reconnector):
+ self.timeout(seconds, reconnector)
+
+
class WebSocketApp:
"""
Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
@@ -97,7 +137,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 +194,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 +215,8 @@ class WebSocketApp:
self.last_ping_tm = 0
self.last_pong_tm = 0
self.subprotocols = subprotocols
+ self.prepared_socket = socket
+ self.has_errored = False
def send(self, data, opcode=ABNF.OPCODE_TEXT):
"""
@@ -214,9 +259,10 @@ class WebSocketApp:
ping_payload="",
http_proxy_host=None, http_proxy_port=None,
http_no_proxy=None, http_proxy_auth=None,
+ http_proxy_timeout=None,
skip_utf8_validation=False,
host=None, origin=None, dispatcher=None,
- suppress_origin=False, proxy_type=None):
+ suppress_origin=False, proxy_type=None, reconnect=None):
"""
Run event loop for WebSocket framework.
@@ -244,6 +290,10 @@ class WebSocketApp:
HTTP proxy port. If not set, set to 80.
http_no_proxy: list
Whitelisted host names that don't use the proxy.
+ http_proxy_timeout: int or float
+ HTTP proxy timeout, default is 60 sec as per python-socks.
+ http_proxy_auth: tuple
+ HTTP proxy auth information. tuple of username and password. Default is None.
skip_utf8_validation: bool
skip utf8 validation.
host: str
@@ -254,13 +304,21 @@ class WebSocketApp:
customize reading data from socket.
suppress_origin: bool
suppress outputting origin header.
+ proxy_type: str
+ type of proxy from: http, socks4, socks4a, socks5, socks5h
+ reconnect: int
+ delay interval when reconnecting
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 reconnect is None:
+ reconnect = RECONNECT
+
if ping_timeout is not None and ping_timeout <= 0:
raise WebSocketException("Ensure ping_timeout > 0")
if ping_interval is not None and ping_interval < 0:
@@ -302,82 +360,105 @@ class WebSocketApp:
# Finally call the callback AFTER all teardown is complete
self._callback(self.on_close, close_status_code, close_reason)
- try:
+ def setSock(reconnecting=False):
self.sock = WebSocket(
self.get_mask_key, sockopt=sockopt, sslopt=sslopt,
fire_cont_frame=self.on_cont_message is not None,
skip_utf8_validation=skip_utf8_validation,
enable_multithread=True)
self.sock.settimeout(getdefaulttimeout())
- self.sock.connect(
- self.url, header=self.header, cookie=self.cookie,
- http_proxy_host=http_proxy_host,
- 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)
-
- self._callback(self.on_open)
-
- if ping_interval:
- event = threading.Event()
- thread = threading.Thread(
- target=self._send_ping, args=(ping_interval, event, ping_payload))
- thread.daemon = True
- thread.start()
-
- def read():
- if not self.keep_running:
- return teardown()
+ try:
+ self.sock.connect(
+ self.url, header=self.header, cookie=self.cookie,
+ http_proxy_host=http_proxy_host,
+ http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy,
+ http_proxy_auth=http_proxy_auth, http_proxy_timeout=http_proxy_timeout,
+ subprotocols=self.subprotocols,
+ host=host, origin=origin, suppress_origin=suppress_origin,
+ proxy_type=proxy_type, socket=self.prepared_socket)
+
+ self._callback(self.on_open)
+
+ _logging.info("websocket connected")
+ dispatcher.read(self.sock.sock, read, check)
+ except (WebSocketConnectionClosedException, ConnectionRefusedError, KeyboardInterrupt, SystemExit, Exception) as e:
+ _logging.error("%s - %s" % (e, reconnect and "reconnecting" or "goodbye"))
+ reconnecting or handleDisconnect(e)
+
+ def read():
+ if not self.keep_running:
+ return teardown()
+ try:
op_code, frame = self.sock.recv_data_frame(True)
- if op_code == ABNF.OPCODE_CLOSE:
- return teardown(frame)
- elif op_code == ABNF.OPCODE_PING:
- self._callback(self.on_ping, frame.data)
- elif op_code == ABNF.OPCODE_PONG:
- self.last_pong_tm = time.time()
- self._callback(self.on_pong, frame.data)
- elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
- self._callback(self.on_data, frame.data,
- frame.opcode, frame.fin)
- self._callback(self.on_cont_message,
- frame.data, frame.fin)
+ except (WebSocketConnectionClosedException, KeyboardInterrupt) as e:
+ if custom_dispatcher:
+ return handleDisconnect(e)
else:
- data = frame.data
- if op_code == ABNF.OPCODE_TEXT:
- data = data.decode("utf-8")
- self._callback(self.on_data, data, frame.opcode, True)
- self._callback(self.on_message, data)
-
- return True
-
- def check():
- if (ping_timeout):
- has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout
- has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
- has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout
-
- if (self.last_ping_tm and
- has_timeout_expired and
- (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)):
- raise WebSocketTimeoutException("ping/pong timed out")
- return True
-
- dispatcher.read(self.sock.sock, read, check)
- except (Exception, KeyboardInterrupt, SystemExit) as e:
+ raise e
+ if op_code == ABNF.OPCODE_CLOSE:
+ return teardown(frame)
+ elif op_code == ABNF.OPCODE_PING:
+ self._callback(self.on_ping, frame.data)
+ elif op_code == ABNF.OPCODE_PONG:
+ self.last_pong_tm = time.time()
+ self._callback(self.on_pong, frame.data)
+ elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
+ self._callback(self.on_data, frame.data,
+ frame.opcode, frame.fin)
+ self._callback(self.on_cont_message,
+ frame.data, frame.fin)
+ else:
+ data = frame.data
+ if op_code == ABNF.OPCODE_TEXT and not skip_utf8_validation:
+ data = data.decode("utf-8")
+ self._callback(self.on_data, data, frame.opcode, True)
+ self._callback(self.on_message, data)
+
+ return True
+
+ def check():
+ if (ping_timeout):
+ has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout
+ has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
+ has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout
+
+ if (self.last_ping_tm and
+ has_timeout_expired and
+ (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)):
+ raise WebSocketTimeoutException("ping/pong timed out")
+ return True
+
+ def handleDisconnect(e):
+ self.has_errored = True
self._callback(self.on_error, e)
if isinstance(e, SystemExit):
# propagate SystemExit further
raise
- teardown()
- return not isinstance(e, KeyboardInterrupt)
-
- def create_dispatcher(self, ping_timeout):
+ if reconnect and not isinstance(e, KeyboardInterrupt):
+ _logging.info("websocket disconnected (retrying in %s seconds) [%s frames in stack]" % (reconnect, len(inspect.stack())))
+ dispatcher.reconnect(reconnect, setSock)
+ else:
+ teardown()
+
+ custom_dispatcher = bool(dispatcher)
+ dispatcher = self.create_dispatcher(ping_timeout, dispatcher, parse_url(self.url)[3])
+
+ if ping_interval:
+ event = threading.Event()
+ thread = threading.Thread(
+ target=self._send_ping, args=(ping_interval, event, ping_payload))
+ thread.daemon = True
+ thread.start()
+
+ setSock()
+ return self.has_errored
+
+ def create_dispatcher(self, ping_timeout, dispatcher=None, is_ssl=False):
+ 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():
+ if is_ssl:
return SSLDispatcher(self, timeout)
return Dispatcher(self, timeout)
diff --git a/websocket/_cookiejar.py b/websocket/_cookiejar.py
index 8785383..5476d1d 100644
--- a/websocket/_cookiejar.py
+++ b/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/websocket/_core.py b/websocket/_core.py
index e26c8b1..1d68829 100644
--- a/websocket/_core.py
+++ b/websocket/_core.py
@@ -1,14 +1,23 @@
-"""
-_core.py
-====================================
-WebSocket Python client
-"""
+import socket
+import struct
+import threading
+import time
+
+# websocket modules
+from ._abnf import *
+from ._exceptions import *
+from ._handshake import *
+from ._http import *
+from ._logging import *
+from ._socket import *
+from ._ssl_compat import *
+from ._utils import *
"""
_core.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.
@@ -22,20 +31,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 socket
-import struct
-import threading
-import time
-
-# websocket modules
-from ._abnf import *
-from ._exceptions import *
-from ._handshake import *
-from ._http import *
-from ._logging import *
-from ._socket import *
-from ._ssl_compat import *
-from ._utils import *
__all__ = ['WebSocket', 'create_connection']
@@ -51,8 +46,11 @@ class WebSocket:
>>> import websocket
>>> ws = websocket.WebSocket()
- >>> ws.connect("ws://echo.websocket.org")
+ >>> ws.connect("ws://echo.websocket.events")
+ >>> ws.recv()
+ 'echo.websocket.events sponsored by Lob.com'
>>> ws.send("Hello, Server")
+ 19
>>> ws.recv()
'Hello, Server'
>>> ws.close()
@@ -208,7 +206,7 @@ class WebSocket:
If you set "header" list object, you can set your own custom header.
>>> ws = WebSocket()
- >>> ws.connect("ws://echo.websocket.org/",
+ >>> ws.connect("ws://echo.websocket.events",
... header=["User-Agent: MyProgram",
... "x-custom: header"])
@@ -238,6 +236,8 @@ class WebSocket:
Whitelisted host names that don't use the proxy.
http_proxy_auth: tuple
HTTP proxy auth information. Tuple of username and password. Default is None.
+ http_proxy_timeout: int or float
+ HTTP proxy timeout, default is 60 sec as per python-socks.
redirect_limit: int
Number of redirects to follow.
subprotocols: list
@@ -250,14 +250,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:
@@ -286,7 +286,7 @@ class WebSocket:
"""
Send the data frame.
- >>> ws = create_connection("ws://echo.websocket.org/")
+ >>> ws = create_connection("ws://echo.websocket.events")
>>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
>>> ws.send_frame(frame)
>>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
@@ -546,7 +546,7 @@ def create_connection(url, timeout=None, class_=WebSocket, **options):
You can customize using 'options'.
If you set "header" list object, you can set your own custom header.
- >>> conn = create_connection("ws://echo.websocket.org/",
+ >>> conn = create_connection("ws://echo.websocket.events",
... header=["User-Agent: MyProgram",
... "x-custom: header"])
@@ -577,6 +577,8 @@ def create_connection(url, timeout=None, class_=WebSocket, **options):
Whitelisted host names that don't use the proxy.
http_proxy_auth: tuple
HTTP proxy auth information. tuple of username and password. Default is None.
+ http_proxy_timeout: int or float
+ HTTP proxy timeout, default is 60 sec as per python-socks.
enable_multithread: bool
Enable lock for multithread.
redirect_limit: int
diff --git a/websocket/_exceptions.py b/websocket/_exceptions.py
index b92b1f4..811d594 100644
--- a/websocket/_exceptions.py
+++ b/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/websocket/_handshake.py b/websocket/_handshake.py
index f9dabb5..07a4cfb 100644
--- a/websocket/_handshake.py
+++ b/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"
@@ -81,30 +81,34 @@ def _get_handshake_headers(resource, host, port, options):
hostport = _pack_hostname(host)
else:
hostport = "%s:%d" % (_pack_hostname(host), port)
- if "host" in options and options["host"] is not None:
+ if options.get("host"):
headers.append("Host: %s" % options["host"])
else:
headers.append("Host: %s" % hostport)
- if "suppress_origin" not in options or not options["suppress_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
+ scheme, url = url.split(":", 1)
+ if not options.get("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)
key = _create_sec_websocket_key()
# Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
- if 'header' not in options or 'Sec-WebSocket-Key' not in options['header']:
- key = _create_sec_websocket_key()
+ if not options.get('header') or 'Sec-WebSocket-Key' not in options['header']:
headers.append("Sec-WebSocket-Key: %s" % key)
else:
key = options['header']['Sec-WebSocket-Key']
- if 'header' not in options 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)
- if 'connection' not in options or options['connection'] is None:
+ if not options.get('connection'):
headers.append('Connection: Upgrade')
else:
headers.append(options['connection'])
@@ -113,8 +117,8 @@ def _get_handshake_headers(resource, host, port, options):
if subprotocols:
headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols))
- if "header" in options:
- header = options["header"]
+ header = options.get("header")
+ if header:
if isinstance(header, dict):
header = [
": ".join([k, v])
diff --git a/websocket/_http.py b/websocket/_http.py
index 603fa00..17d3f8a 100644
--- a/websocket/_http.py
+++ b/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.
@@ -23,7 +23,7 @@ import sys
from ._exceptions import *
from ._logging import *
-from ._socket import*
+from ._socket import *
from ._ssl_compat import *
from ._url import *
@@ -59,7 +59,7 @@ class proxy_info:
self.no_proxy = options.get("http_no_proxy", None)
self.proxy_protocol = options.get("proxy_type", "http")
# Note: If timeout not specified, default python-socks timeout is 60 seconds
- self.proxy_timeout = options.get("timeout", None)
+ self.proxy_timeout = options.get("http_proxy_timeout", None)
if self.proxy_protocol not in ['http', 'socks4', 'socks4a', 'socks5', 'socks5h']:
raise ProxyError("Only http, socks4, socks5 proxy protocols are supported")
else:
@@ -114,22 +114,22 @@ def connect(url, options, proxy, socket):
if proxy.proxy_host and not socket and not (proxy.proxy_protocol == "http"):
return _start_proxied_socket(url, options, proxy)
- hostname, port, resource, is_secure = parse_url(url)
+ hostname, port_from_url, resource, is_secure = parse_url(url)
if socket:
- return socket, (hostname, port, resource)
+ return socket, (hostname, port_from_url, resource)
addrinfo_list, need_tunnel, auth = _get_addrinfo_list(
- hostname, port, is_secure, proxy)
+ hostname, port_from_url, is_secure, proxy)
if not addrinfo_list:
raise WebSocketException(
- "Host not found.: " + hostname + ":" + str(port))
+ "Host not found.: " + hostname + ":" + str(port_from_url))
sock = None
try:
sock = _open_socket(addrinfo_list, options.sockopt, options.timeout)
if need_tunnel:
- sock = _tunnel(sock, hostname, port, auth)
+ sock = _tunnel(sock, hostname, port_from_url, auth)
if is_secure:
if HAVE_SSL:
@@ -137,7 +137,7 @@ def connect(url, options, proxy, socket):
else:
raise WebSocketException("SSL not available.")
- return sock, (hostname, port, resource)
+ return sock, (hostname, port_from_url, resource)
except:
if sock:
sock.close()
@@ -184,19 +184,16 @@ def _open_socket(addrinfo_list, sockopt, timeout):
try:
sock.connect(address)
except socket.error as error:
+ sock.close()
error.remote_ip = str(address[0])
try:
- eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED)
- except:
- eConnRefused = (errno.ECONNREFUSED, )
- if error.errno == errno.EINTR:
- continue
- elif error.errno in eConnRefused:
+ eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED, errno.ENETUNREACH)
+ except AttributeError:
+ eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH)
+ if error.errno in eConnRefused:
err = error
continue
else:
- if sock:
- sock.close()
raise error
else:
break
diff --git a/websocket/_logging.py b/websocket/_logging.py
index 480d43b..3921111 100644
--- a/websocket/_logging.py
+++ b/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:
@@ -38,7 +35,7 @@ __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
"isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
-def enableTrace(traceable, handler=logging.StreamHandler()):
+def enableTrace(traceable, handler=logging.StreamHandler(), level="DEBUG"):
"""
Turn on/off the traceability.
@@ -51,7 +48,7 @@ def enableTrace(traceable, handler=logging.StreamHandler()):
_traceEnabled = traceable
if traceable:
_logger.addHandler(handler)
- _logger.setLevel(logging.DEBUG)
+ _logger.setLevel(getattr(logging, level))
def dump(title, message):
@@ -73,6 +70,10 @@ def debug(msg):
_logger.debug(msg)
+def info(msg):
+ _logger.info(msg)
+
+
def trace(msg):
if _traceEnabled:
_logger.debug(msg)
diff --git a/websocket/_socket.py b/websocket/_socket.py
index 4d9cc09..54e6399 100644
--- a/websocket/_socket.py
+++ b/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/websocket/_ssl_compat.py b/websocket/_ssl_compat.py
index f4af524..e227840 100644
--- a/websocket/_ssl_compat.py
+++ b/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/websocket/_url.py b/websocket/_url.py
index f2a5501..2d3d265 100644
--- a/websocket/_url.py
+++ b/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/websocket/_utils.py b/websocket/_utils.py
index 21fc437..fdcf345 100644
--- a/websocket/_utils.py
+++ b/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/websocket/_wsdump.py b/websocket/_wsdump.py
index 4d15f41..860ac34 100755
--- a/websocket/_wsdump.py
+++ b/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/websocket/tests/data/header03.txt b/websocket/tests/data/header03.txt
index 030e13a..1a81dc7 100644
--- a/websocket/tests/data/header03.txt
+++ b/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/websocket/tests/test_abnf.py b/websocket/tests/test_abnf.py
index 7f156dc..7c9d89d 100644
--- a/websocket/tests/test_abnf.py
+++ b/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/websocket/tests/test_app.py b/websocket/tests/test_app.py
index cd1146b..5526d3e 100644
--- a/websocket/tests/test_app.py
+++ b/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,57 @@ 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")
+ @unittest.skipUnless(False, "Test disabled for now (requires rel)")
+ 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") # doesn't work
+# app.run_forever(dispatcher=rel) # would work
+# rel.dispatch()
+
+ @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 +140,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,8 +190,8 @@ class WebSocketAppTest(unittest.TestCase):
def testOpcodeBinary(self):
""" Test WebSocketApp binary opcode
"""
-
- app = ws.WebSocketApp('streaming.vn.teslamotors.com/streaming/')
+ # The lack of wss:// in the URL below is on purpose
+ app = ws.WebSocketApp('wss://streaming.vn.teslamotors.com/streaming/')
app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload")
@unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
diff --git a/websocket/tests/test_cookiejar.py b/websocket/tests/test_cookiejar.py
index 5bf1fca..559b2e0 100644
--- a/websocket/tests/test_cookiejar.py
+++ b/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/websocket/tests/test_http.py b/websocket/tests/test_http.py
index fda467d..ffcbde2 100644
--- a/websocket/tests/test_http.py
+++ b/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:
@@ -105,15 +105,15 @@ class HttpTest(unittest.TestCase):
if ws._http.HAVE_PYTHON_SOCKS:
# Need this check, otherwise case where python_socks is not installed triggers
# websocket._exceptions.WebSocketException: Python Socks is needed for SOCKS proxying but is not available
- self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4", timeout=1))
- self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4a", timeout=1))
- self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5", timeout=1))
- self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h", timeout=1))
- self.assertRaises(ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=9999, proxy_type="socks4", timeout=1), None)
+ self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4", http_proxy_timeout=1))
+ self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4a", http_proxy_timeout=1))
+ self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5", http_proxy_timeout=1))
+ self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h", http_proxy_timeout=1))
+ self.assertRaises(ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=9999, proxy_type="socks4", http_proxy_timeout=1), None)
self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http"))
self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http"))
- self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=9999, proxy_type="http", timeout=1), None)
+ self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=9999, proxy_type="http", http_proxy_timeout=1), None)
self.assertEqual(
connect("wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http"), True),
(True, ("google.com", 443, "/")))
diff --git a/websocket/tests/test_url.py b/websocket/tests/test_url.py
index ad3a3b1..7e155fd 100644
--- a/websocket/tests/test_url.py
+++ b/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/websocket/tests/test_websocket.py b/websocket/tests/test_websocket.py
index 8b34aa5..d47d73e 100644
--- a/websocket/tests/test_websocket.py
+++ b/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")
@@ -434,7 +432,7 @@ class HandshakeTest(unittest.TestCase):
self.assertRaises(ws._exceptions.WebSocketBadStatusException,
websock3.connect, "wss://api.bitfinex.com/ws/2", cookie="chocolate",
origin="testing_websockets.com",
- host="echo.websocket.org/websocket-client-test",
+ host="echo.websocket.events/websocket-client-test",
subprotocols=["testproto"],
connection="Upgrade",
header={"CustomHeader1":"123",
diff --git a/websocket_client.egg-info/PKG-INFO b/websocket_client.egg-info/PKG-INFO
index 116110c..b0b7e1d 100644
--- a/websocket_client.egg-info/PKG-INFO
+++ b/websocket_client.egg-info/PKG-INFO
@@ -1,35 +1,34 @@
Metadata-Version: 2.1
Name: websocket-client
-Version: 1.2.3
+Version: 1.4.2
Summary: WebSocket client for Python with low level API options
Home-page: https://github.com/websocket-client/websocket-client.git
+Download-URL: https://github.com/websocket-client/websocket-client/releases
Author: liris
Author-email: liris.pp@gmail.com
License: Apache-2.0
-Download-URL: https://github.com/websocket-client/websocket-client/releases
Project-URL: Documentation, https://websocket-client.readthedocs.io/
Project-URL: Source, https://github.com/websocket-client/websocket-client/
Keywords: websockets client
-Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Intended Audience :: Developers
-Requires-Python: >=3.6
+Requires-Python: >=3.7
Description-Content-Type: text/markdown
-Provides-Extra: test
-Provides-Extra: optional
Provides-Extra: docs
+Provides-Extra: optional
+Provides-Extra: test
License-File: LICENSE
[![docs](https://readthedocs.org/projects/websocket-client/badge/?style=flat)](https://websocket-client.readthedocs.io/)
@@ -59,7 +58,7 @@ Please see the [contribution guidelines](https://github.com/websocket-client/web
## Installation
You can use either `python3 setup.py install` or `pip3 install websocket-client`
-to install. This module is tested on Python 3.6+.
+to install. This module is tested on Python 3.7+.
There are several optional dependencies that can be installed to enable
specific websocket-client features.
@@ -70,6 +69,9 @@ specific websocket-client features.
- To install `Sphinx` and `sphinx_rtd_theme` to build project documentation, use:
`pip3 install websocket-client[docs]`
+While not a strict dependency, [rel](https://github.com/bubbleboy14/registeredeventlistener)
+is useful when using `run_forever` with automatic reconnect. Install rel with `pip3 install rel`.
+
Footnote: Some shells, such as zsh, require you to escape the `[` and `]` characters with a `\`.
## Usage Tips
@@ -102,13 +104,28 @@ Many more examples are found in the
### Long-lived Connection
Most real-world WebSockets situations involve longer-lived connections.
-The WebSocketApp `run_forever` loop automatically tries to reconnect when a
-connection is lost, and provides a variety of event-based connection controls.
+The WebSocketApp `run_forever` loop will automatically try to reconnect
+to an open WebSocket connection when a network
+connection is lost if it is provided with:
+
+- a `dispatcher` argument (async dispatcher like rel or pyevent)
+- a non-zero `reconnect` argument (delay between disconnection and attempted reconnection)
+
+`run_forever` provides a variety of event-based connection controls
+using callbacks like `on_message` and `on_error`.
+`run_forever` does not automatically reconnect if the server
+closes the WebSocket gracefully (returning
+[a standard websocket close code](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1)).
+Customizing behavior when the server closes
+the WebSocket should be handled in the `on_close` callback.
+This example uses [rel](https://github.com/bubbleboy14/registeredeventlistener)
+for the dispatcher to provide automatic reconnection.
```python
import websocket
import _thread
import time
+import rel
def on_message(ws, message):
print(message)
@@ -120,24 +137,19 @@ def on_close(ws, close_status_code, close_msg):
print("### closed ###")
def on_open(ws):
- def run(*args):
- for i in range(3):
- time.sleep(1)
- ws.send("Hello %d" % i)
- time.sleep(1)
- ws.close()
- print("thread terminating...")
- _thread.start_new_thread(run, ())
+ print("Opened connection")
if __name__ == "__main__":
websocket.enableTrace(True)
- ws = websocket.WebSocketApp("ws://echo.websocket.org/",
+ ws = websocket.WebSocketApp("wss://api.gemini.com/v1/marketdata/BTCUSD",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close)
- ws.run_forever()
+ ws.run_forever(dispatcher=rel, reconnect=5) # Set dispatcher to automatic reconnection, 5 second reconnect delay if connection closed unexpectedly
+ rel.signal(2, rel.abort) # Keyboard Interrupt
+ rel.dispatch()
```
### Short-lived Connection
@@ -148,7 +160,9 @@ server is running and responds properly to a specific request.
```python
from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/")
+
+ws = create_connection("ws://echo.websocket.events/")
+print(ws.recv())
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
@@ -157,18 +171,3 @@ result = ws.recv()
print("Received '%s'" % result)
ws.close()
```
-
-If you want to customize socket options, set sockopt, as seen below:
-
-```python
-from websocket import create_connection
-ws = create_connection("ws://echo.websocket.org/",
- sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY),))
-```
-
-### Acknowledgements
-
-Thanks to @battlemidget and @ralphbean for helping migrate this project to
-Python 3.
-
-
diff --git a/websocket_client.egg-info/SOURCES.txt b/websocket_client.egg-info/SOURCES.txt
index b62ab51..d613f2b 100644
--- a/websocket_client.egg-info/SOURCES.txt
+++ b/websocket_client.egg-info/SOURCES.txt
@@ -6,6 +6,7 @@ setup.cfg
setup.py
examples/echo_client.py
examples/echoapp_client.py
+examples/rel_client.py
websocket/__init__.py
websocket/_abnf.py
websocket/_app.py
diff --git a/websocket_client.egg-info/entry_points.txt b/websocket_client.egg-info/entry_points.txt
index 2c30a29..45c854e 100644
--- a/websocket_client.egg-info/entry_points.txt
+++ b/websocket_client.egg-info/entry_points.txt
@@ -1,3 +1,2 @@
[console_scripts]
wsdump = websocket._wsdump:main
-
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.4.2.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.4.2.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.4.2.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.4.2.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.4.2.egg-info/top_level.txt -rw-r--r-- root/root /usr/share/doc/python3-websocket/examples/rel_client.py
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.2.3.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.2.3.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.2.3.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.2.3.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/websocket_client-1.2.3.egg-info/top_level.txt
No differences were encountered in the control files