aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/websocket-client/py3
diff options
context:
space:
mode:
authoralexv-smirnov <alex@ydb.tech>2023-12-01 12:02:50 +0300
committeralexv-smirnov <alex@ydb.tech>2023-12-01 13:28:10 +0300
commit0e578a4c44d4abd539d9838347b9ebafaca41dfb (patch)
treea0c1969c37f818c830ebeff9c077eacf30be6ef8 /contrib/python/websocket-client/py3
parent84f2d3d4cc985e63217cff149bd2e6d67ae6fe22 (diff)
downloadydb-0e578a4c44d4abd539d9838347b9ebafaca41dfb.tar.gz
Change "ya.make"
Diffstat (limited to 'contrib/python/websocket-client/py3')
-rw-r--r--contrib/python/websocket-client/py3/.dist-info/METADATA184
-rw-r--r--contrib/python/websocket-client/py3/.dist-info/entry_points.txt3
-rw-r--r--contrib/python/websocket-client/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/websocket-client/py3/LICENSE203
-rw-r--r--contrib/python/websocket-client/py3/README.md141
-rw-r--r--contrib/python/websocket-client/py3/tests/ya.make27
-rw-r--r--contrib/python/websocket-client/py3/websocket/__init__.py26
-rw-r--r--contrib/python/websocket-client/py3/websocket/_abnf.py426
-rw-r--r--contrib/python/websocket-client/py3/websocket/_app.py558
-rw-r--r--contrib/python/websocket-client/py3/websocket/_cookiejar.py66
-rw-r--r--contrib/python/websocket-client/py3/websocket/_core.py611
-rw-r--r--contrib/python/websocket-client/py3/websocket/_exceptions.py80
-rw-r--r--contrib/python/websocket-client/py3/websocket/_handshake.py197
-rw-r--r--contrib/python/websocket-client/py3/websocket/_http.py340
-rw-r--r--contrib/python/websocket-client/py3/websocket/_logging.py93
-rw-r--r--contrib/python/websocket-client/py3/websocket/_socket.py181
-rw-r--r--contrib/python/websocket-client/py3/websocket/_ssl_compat.py39
-rw-r--r--contrib/python/websocket-client/py3/websocket/_url.py169
-rw-r--r--contrib/python/websocket-client/py3/websocket/_utils.py106
-rw-r--r--contrib/python/websocket-client/py3/websocket/_wsdump.py231
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/__init__.py0
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/data/header01.txt6
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/data/header02.txt6
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/data/header03.txt8
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_abnf.py89
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_app.py299
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_cookiejar.py116
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_http.py177
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_url.py319
-rw-r--r--contrib/python/websocket-client/py3/websocket/tests/test_websocket.py456
-rw-r--r--contrib/python/websocket-client/py3/ya.make40
31 files changed, 5198 insertions, 0 deletions
diff --git a/contrib/python/websocket-client/py3/.dist-info/METADATA b/contrib/python/websocket-client/py3/.dist-info/METADATA
new file mode 100644
index 0000000000..3925b4a17d
--- /dev/null
+++ b/contrib/python/websocket-client/py3/.dist-info/METADATA
@@ -0,0 +1,184 @@
+Metadata-Version: 2.1
+Name: websocket-client
+Version: 1.6.4
+Summary: WebSocket client for Python with low level API options
+Home-page: https://github.com/websocket-client/websocket-client.git
+Author: liris
+Author-email: liris.pp@gmail.com
+Maintainer: engn33r
+Maintainer-email: websocket.client@proton.me
+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.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+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.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Provides-Extra: docs
+Requires-Dist: Sphinx (>=6.0) ; extra == 'docs'
+Requires-Dist: sphinx-rtd-theme (>=1.1.0) ; extra == 'docs'
+Provides-Extra: optional
+Requires-Dist: python-socks ; extra == 'optional'
+Requires-Dist: wsaccel ; extra == 'optional'
+Provides-Extra: test
+Requires-Dist: websockets ; extra == 'test'
+
+[![docs](https://readthedocs.org/projects/websocket-client/badge/?style=flat)](https://websocket-client.readthedocs.io/)
+[![Build Status](https://github.com/websocket-client/websocket-client/actions/workflows/build.yml/badge.svg)](https://github.com/websocket-client/websocket-client/actions/workflows/build.yml)
+[![codecov](https://codecov.io/gh/websocket-client/websocket-client/branch/master/graph/badge.svg?token=pcXhUQwiL3)](https://codecov.io/gh/websocket-client/websocket-client)
+[![PyPI Downloads](https://pepy.tech/badge/websocket-client)](https://pepy.tech/project/websocket-client)
+[![PyPI version](https://img.shields.io/pypi/v/websocket_client)](https://pypi.org/project/websocket_client/)
+
+# websocket-client
+
+websocket-client is a WebSocket client for Python. It provides access
+to low level APIs for WebSockets. websocket-client implements version
+[hybi-13](https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13)
+of the WebSocket protocol. This client does not currently support the
+permessage-deflate extension from
+[RFC 7692](https://tools.ietf.org/html/rfc7692).
+
+## Documentation
+
+This project's documentation can be found at
+[https://websocket-client.readthedocs.io/](https://websocket-client.readthedocs.io/)
+
+## Contributing
+
+Please see the [contribution guidelines](https://github.com/websocket-client/websocket-client/blob/master/CONTRIBUTING.md)
+
+## Installation
+
+You can use either `python3 setup.py install` or `pip3 install websocket-client`
+to install. This module is tested on Python 3.8+.
+
+There are several optional dependencies that can be installed to enable
+specific websocket-client features.
+- To install `python-socks` for proxy usage and `wsaccel` for a minor performance boost, use:
+ `pip3 install websocket-client[optional]`
+- To install `websockets` to run unit tests using the local echo server, use:
+ `pip3 install websocket-client[test]`
+- 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
+
+Check out the documentation's FAQ for additional guidelines:
+[https://websocket-client.readthedocs.io/en/latest/faq.html](https://websocket-client.readthedocs.io/en/latest/faq.html)
+
+Known issues with this library include lack of WebSocket Compression
+support (RFC 7692) and [minimal threading documentation/support](https://websocket-client.readthedocs.io/en/latest/threading.html).
+
+## Performance
+
+The `send` and `validate_utf8` methods can sometimes be bottleneck.
+You can disable UTF8 validation in this library (and receive a
+performance enhancement) with the `skip_utf8_validation` parameter.
+If you want to get better performance, install wsaccel. While
+websocket-client does not depend on wsaccel, it will be used if
+available. wsaccel doubles the speed of UTF8 validation and
+offers a very minor 10% performance boost when masking the
+payload data as part of the `send` process. Numpy used to
+be a suggested performance enhancement alternative, but
+[issue #687](https://github.com/websocket-client/websocket-client/issues/687)
+found it didn't help.
+
+## Examples
+
+Many more examples are found in the
+[examples documentation](https://websocket-client.readthedocs.io/en/latest/examples.html).
+
+### Long-lived Connection
+
+Most real-world WebSockets situations involve longer-lived connections.
+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)).
+[This is the logic](https://github.com/websocket-client/websocket-client/pull/838#issuecomment-1228454826) behind the decision.
+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)
+
+def on_error(ws, error):
+ print(error)
+
+def on_close(ws, close_status_code, close_msg):
+ print("### closed ###")
+
+def on_open(ws):
+ print("Opened connection")
+
+if __name__ == "__main__":
+ websocket.enableTrace(True)
+ 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(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
+
+This is if you want to communicate a short message and disconnect
+immediately when done. For example, if you want to confirm that a WebSocket
+server is running and responds properly to a specific request.
+
+```python
+from websocket import create_connection
+
+ws = create_connection("ws://echo.websocket.events/")
+print(ws.recv())
+print("Sending 'Hello, World'...")
+ws.send("Hello, World")
+print("Sent")
+print("Receiving...")
+result = ws.recv()
+print("Received '%s'" % result)
+ws.close()
+```
+
+
diff --git a/contrib/python/websocket-client/py3/.dist-info/entry_points.txt b/contrib/python/websocket-client/py3/.dist-info/entry_points.txt
new file mode 100644
index 0000000000..2c30a29b85
--- /dev/null
+++ b/contrib/python/websocket-client/py3/.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+wsdump = websocket._wsdump:main
+
diff --git a/contrib/python/websocket-client/py3/.dist-info/top_level.txt b/contrib/python/websocket-client/py3/.dist-info/top_level.txt
new file mode 100644
index 0000000000..ca4cb0cf82
--- /dev/null
+++ b/contrib/python/websocket-client/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+websocket
diff --git a/contrib/python/websocket-client/py3/LICENSE b/contrib/python/websocket-client/py3/LICENSE
new file mode 100644
index 0000000000..88a0d3eb19
--- /dev/null
+++ b/contrib/python/websocket-client/py3/LICENSE
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2023 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.
+
diff --git a/contrib/python/websocket-client/py3/README.md b/contrib/python/websocket-client/py3/README.md
new file mode 100644
index 0000000000..daa39bca8a
--- /dev/null
+++ b/contrib/python/websocket-client/py3/README.md
@@ -0,0 +1,141 @@
+[![docs](https://readthedocs.org/projects/websocket-client/badge/?style=flat)](https://websocket-client.readthedocs.io/)
+[![Build Status](https://github.com/websocket-client/websocket-client/actions/workflows/build.yml/badge.svg)](https://github.com/websocket-client/websocket-client/actions/workflows/build.yml)
+[![codecov](https://codecov.io/gh/websocket-client/websocket-client/branch/master/graph/badge.svg?token=pcXhUQwiL3)](https://codecov.io/gh/websocket-client/websocket-client)
+[![PyPI Downloads](https://pepy.tech/badge/websocket-client)](https://pepy.tech/project/websocket-client)
+[![PyPI version](https://img.shields.io/pypi/v/websocket_client)](https://pypi.org/project/websocket_client/)
+
+# websocket-client
+
+websocket-client is a WebSocket client for Python. It provides access
+to low level APIs for WebSockets. websocket-client implements version
+[hybi-13](https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13)
+of the WebSocket protocol. This client does not currently support the
+permessage-deflate extension from
+[RFC 7692](https://tools.ietf.org/html/rfc7692).
+
+## Documentation
+
+This project's documentation can be found at
+[https://websocket-client.readthedocs.io/](https://websocket-client.readthedocs.io/)
+
+## Contributing
+
+Please see the [contribution guidelines](https://github.com/websocket-client/websocket-client/blob/master/CONTRIBUTING.md)
+
+## Installation
+
+You can use either `python3 setup.py install` or `pip3 install websocket-client`
+to install. This module is tested on Python 3.8+.
+
+There are several optional dependencies that can be installed to enable
+specific websocket-client features.
+- To install `python-socks` for proxy usage and `wsaccel` for a minor performance boost, use:
+ `pip3 install websocket-client[optional]`
+- To install `websockets` to run unit tests using the local echo server, use:
+ `pip3 install websocket-client[test]`
+- 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
+
+Check out the documentation's FAQ for additional guidelines:
+[https://websocket-client.readthedocs.io/en/latest/faq.html](https://websocket-client.readthedocs.io/en/latest/faq.html)
+
+Known issues with this library include lack of WebSocket Compression
+support (RFC 7692) and [minimal threading documentation/support](https://websocket-client.readthedocs.io/en/latest/threading.html).
+
+## Performance
+
+The `send` and `validate_utf8` methods can sometimes be bottleneck.
+You can disable UTF8 validation in this library (and receive a
+performance enhancement) with the `skip_utf8_validation` parameter.
+If you want to get better performance, install wsaccel. While
+websocket-client does not depend on wsaccel, it will be used if
+available. wsaccel doubles the speed of UTF8 validation and
+offers a very minor 10% performance boost when masking the
+payload data as part of the `send` process. Numpy used to
+be a suggested performance enhancement alternative, but
+[issue #687](https://github.com/websocket-client/websocket-client/issues/687)
+found it didn't help.
+
+## Examples
+
+Many more examples are found in the
+[examples documentation](https://websocket-client.readthedocs.io/en/latest/examples.html).
+
+### Long-lived Connection
+
+Most real-world WebSockets situations involve longer-lived connections.
+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)).
+[This is the logic](https://github.com/websocket-client/websocket-client/pull/838#issuecomment-1228454826) behind the decision.
+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)
+
+def on_error(ws, error):
+ print(error)
+
+def on_close(ws, close_status_code, close_msg):
+ print("### closed ###")
+
+def on_open(ws):
+ print("Opened connection")
+
+if __name__ == "__main__":
+ websocket.enableTrace(True)
+ 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(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
+
+This is if you want to communicate a short message and disconnect
+immediately when done. For example, if you want to confirm that a WebSocket
+server is running and responds properly to a specific request.
+
+```python
+from websocket import create_connection
+
+ws = create_connection("ws://echo.websocket.events/")
+print(ws.recv())
+print("Sending 'Hello, World'...")
+ws.send("Hello, World")
+print("Sent")
+print("Receiving...")
+result = ws.recv()
+print("Received '%s'" % result)
+ws.close()
+```
diff --git a/contrib/python/websocket-client/py3/tests/ya.make b/contrib/python/websocket-client/py3/tests/ya.make
new file mode 100644
index 0000000000..df3343f388
--- /dev/null
+++ b/contrib/python/websocket-client/py3/tests/ya.make
@@ -0,0 +1,27 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/websocket-client
+)
+
+DATA(
+ arcadia/contrib/python/websocket-client/py3/websocket/tests/data
+)
+
+SRCDIR(
+ contrib/python/websocket-client/py3/websocket/tests
+)
+
+TEST_SRCS(
+ __init__.py
+ test_abnf.py
+ test_app.py
+ test_cookiejar.py
+ test_http.py
+ test_url.py
+ test_websocket.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/websocket-client/py3/websocket/__init__.py b/contrib/python/websocket-client/py3/websocket/__init__.py
new file mode 100644
index 0000000000..c186ace8cc
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/__init__.py
@@ -0,0 +1,26 @@
+"""
+__init__.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+from ._abnf import *
+from ._app import WebSocketApp, setReconnect
+from ._core import *
+from ._exceptions import *
+from ._logging import *
+from ._socket import *
+
+__version__ = "1.6.4"
diff --git a/contrib/python/websocket-client/py3/websocket/_abnf.py b/contrib/python/websocket-client/py3/websocket/_abnf.py
new file mode 100644
index 0000000000..a1c6f5a6fe
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_abnf.py
@@ -0,0 +1,426 @@
+import array
+import os
+import struct
+import sys
+
+from threading import Lock
+from typing import Callable, Union
+
+from ._exceptions import *
+from ._utils import validate_utf8
+
+"""
+_abnf.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+try:
+ # If wsaccel is available, use compiled routines to mask data.
+ # wsaccel only provides around a 10% speed boost compared
+ # to the websocket-client _mask() implementation.
+ # Note that wsaccel is unmaintained.
+ from wsaccel.xormask import XorMaskerSimple
+
+ def _mask(_m, _d) -> bytes:
+ return XorMaskerSimple(_m).process(_d)
+
+except ImportError:
+ # wsaccel is not available, use websocket-client _mask()
+ native_byteorder = sys.byteorder
+
+ def _mask(mask_value: array.array, data_value: array.array) -> bytes:
+ datalen = len(data_value)
+ int_data_value = int.from_bytes(data_value, native_byteorder)
+ int_mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder)
+ return (int_data_value ^ int_mask_value).to_bytes(datalen, native_byteorder)
+
+
+__all__ = [
+ 'ABNF', 'continuous_frame', 'frame_buffer',
+ 'STATUS_NORMAL',
+ 'STATUS_GOING_AWAY',
+ 'STATUS_PROTOCOL_ERROR',
+ 'STATUS_UNSUPPORTED_DATA_TYPE',
+ 'STATUS_STATUS_NOT_AVAILABLE',
+ 'STATUS_ABNORMAL_CLOSED',
+ 'STATUS_INVALID_PAYLOAD',
+ 'STATUS_POLICY_VIOLATION',
+ 'STATUS_MESSAGE_TOO_BIG',
+ 'STATUS_INVALID_EXTENSION',
+ 'STATUS_UNEXPECTED_CONDITION',
+ 'STATUS_BAD_GATEWAY',
+ 'STATUS_TLS_HANDSHAKE_ERROR',
+]
+
+# closing frame status codes.
+STATUS_NORMAL = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA_TYPE = 1003
+STATUS_STATUS_NOT_AVAILABLE = 1005
+STATUS_ABNORMAL_CLOSED = 1006
+STATUS_INVALID_PAYLOAD = 1007
+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
+
+VALID_CLOSE_STATUS = (
+ STATUS_NORMAL,
+ STATUS_GOING_AWAY,
+ STATUS_PROTOCOL_ERROR,
+ STATUS_UNSUPPORTED_DATA_TYPE,
+ STATUS_INVALID_PAYLOAD,
+ STATUS_POLICY_VIOLATION,
+ STATUS_MESSAGE_TOO_BIG,
+ STATUS_INVALID_EXTENSION,
+ STATUS_UNEXPECTED_CONDITION,
+ STATUS_SERVICE_RESTART,
+ STATUS_TRY_AGAIN_LATER,
+ STATUS_BAD_GATEWAY,
+)
+
+
+class ABNF:
+ """
+ ABNF frame class.
+ See http://tools.ietf.org/html/rfc5234
+ and http://tools.ietf.org/html/rfc6455#section-5.2
+ """
+
+ # operation code values.
+ OPCODE_CONT = 0x0
+ OPCODE_TEXT = 0x1
+ OPCODE_BINARY = 0x2
+ OPCODE_CLOSE = 0x8
+ OPCODE_PING = 0x9
+ OPCODE_PONG = 0xa
+
+ # available operation code value tuple
+ OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
+ OPCODE_PING, OPCODE_PONG)
+
+ # opcode human readable string
+ OPCODE_MAP = {
+ OPCODE_CONT: "cont",
+ OPCODE_TEXT: "text",
+ OPCODE_BINARY: "binary",
+ OPCODE_CLOSE: "close",
+ OPCODE_PING: "ping",
+ OPCODE_PONG: "pong"
+ }
+
+ # data length threshold.
+ LENGTH_7 = 0x7e
+ LENGTH_16 = 1 << 16
+ LENGTH_63 = 1 << 63
+
+ def __init__(self, fin: int = 0, rsv1: int = 0, rsv2: int = 0, rsv3: int = 0,
+ opcode: int = OPCODE_TEXT, mask: int = 1, data: Union[str, bytes] = "") -> None:
+ """
+ Constructor for ABNF. Please check RFC for arguments.
+ """
+ self.fin = fin
+ self.rsv1 = rsv1
+ self.rsv2 = rsv2
+ self.rsv3 = rsv3
+ self.opcode = opcode
+ self.mask = mask
+ if data is None:
+ data = ""
+ self.data = data
+ self.get_mask_key = os.urandom
+
+ def validate(self, skip_utf8_validation: bool = False) -> None:
+ """
+ Validate the ABNF frame.
+
+ Parameters
+ ----------
+ skip_utf8_validation: skip utf8 validation.
+ """
+ if self.rsv1 or self.rsv2 or self.rsv3:
+ raise WebSocketProtocolException("rsv is not implemented, yet")
+
+ if self.opcode not in ABNF.OPCODES:
+ raise WebSocketProtocolException("Invalid opcode %r", self.opcode)
+
+ if self.opcode == ABNF.OPCODE_PING and not self.fin:
+ raise WebSocketProtocolException("Invalid ping frame.")
+
+ if self.opcode == ABNF.OPCODE_CLOSE:
+ l = len(self.data)
+ if not l:
+ return
+ if l == 1 or l >= 126:
+ raise WebSocketProtocolException("Invalid close frame.")
+ if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]):
+ raise WebSocketProtocolException("Invalid close frame.")
+
+ code = 256 * self.data[0] + self.data[1]
+ if not self._is_valid_close_status(code):
+ raise WebSocketProtocolException("Invalid close opcode %r", code)
+
+ @staticmethod
+ def _is_valid_close_status(code: int) -> bool:
+ return code in VALID_CLOSE_STATUS or (3000 <= code < 5000)
+
+ def __str__(self) -> str:
+ return "fin=" + str(self.fin) \
+ + " opcode=" + str(self.opcode) \
+ + " data=" + str(self.data)
+
+ @staticmethod
+ def create_frame(data: Union[bytes, str], opcode: int, fin: int = 1) -> 'ABNF':
+ """
+ Create frame to send text, binary and other data.
+
+ Parameters
+ ----------
+ data: str
+ data to send. This is string value(byte array).
+ If opcode is OPCODE_TEXT and this value is unicode,
+ data value is converted into unicode string, automatically.
+ opcode: int
+ operation code. please see OPCODE_MAP.
+ fin: int
+ fin flag. if set to 0, create continue fragmentation.
+ """
+ if opcode == ABNF.OPCODE_TEXT and isinstance(data, str):
+ data = data.encode("utf-8")
+ # mask must be set if send data from client
+ return ABNF(fin, 0, 0, 0, opcode, 1, data)
+
+ def format(self) -> bytes:
+ """
+ Format this object to string(byte array) to send data to server.
+ """
+ if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
+ raise ValueError("not 0 or 1")
+ if self.opcode not in ABNF.OPCODES:
+ raise ValueError("Invalid OPCODE")
+ length = len(self.data)
+ if length >= ABNF.LENGTH_63:
+ raise ValueError("data is too long")
+
+ frame_header = chr(self.fin << 7 |
+ self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 |
+ self.opcode).encode('latin-1')
+ if length < ABNF.LENGTH_7:
+ frame_header += chr(self.mask << 7 | length).encode('latin-1')
+ elif length < ABNF.LENGTH_16:
+ frame_header += chr(self.mask << 7 | 0x7e).encode('latin-1')
+ frame_header += struct.pack("!H", length)
+ else:
+ frame_header += chr(self.mask << 7 | 0x7f).encode('latin-1')
+ frame_header += struct.pack("!Q", length)
+
+ if not self.mask:
+ return frame_header + self.data
+ else:
+ mask_key = self.get_mask_key(4)
+ return frame_header + self._get_masked(mask_key)
+
+ def _get_masked(self, mask_key: Union[str, bytes]) -> bytes:
+ s = ABNF.mask(mask_key, self.data)
+
+ if isinstance(mask_key, str):
+ mask_key = mask_key.encode('utf-8')
+
+ return mask_key + s
+
+ @staticmethod
+ def mask(mask_key: Union[str, bytes], data: Union[str, bytes]) -> bytes:
+ """
+ Mask or unmask data. Just do xor for each byte
+
+ Parameters
+ ----------
+ mask_key: bytes or str
+ 4 byte mask.
+ data: bytes or str
+ data to mask/unmask.
+ """
+ if data is None:
+ data = ""
+
+ if isinstance(mask_key, str):
+ mask_key = mask_key.encode('latin-1')
+
+ if isinstance(data, str):
+ data = data.encode('latin-1')
+
+ return _mask(array.array("B", mask_key), array.array("B", data))
+
+
+class frame_buffer:
+ _HEADER_MASK_INDEX = 5
+ _HEADER_LENGTH_INDEX = 6
+
+ def __init__(self, recv_fn: Callable[[int], int], skip_utf8_validation: bool) -> None:
+ self.recv = recv_fn
+ self.skip_utf8_validation = skip_utf8_validation
+ # Buffers over the packets from the layer beneath until desired amount
+ # bytes of bytes are received.
+ self.recv_buffer = []
+ self.clear()
+ self.lock = Lock()
+
+ def clear(self) -> None:
+ self.header = None
+ self.length = None
+ self.mask = None
+
+ def has_received_header(self) -> bool:
+ return self.header is None
+
+ def recv_header(self) -> None:
+ header = self.recv_strict(2)
+ b1 = header[0]
+ fin = b1 >> 7 & 1
+ rsv1 = b1 >> 6 & 1
+ rsv2 = b1 >> 5 & 1
+ rsv3 = b1 >> 4 & 1
+ opcode = b1 & 0xf
+ b2 = header[1]
+ has_mask = b2 >> 7 & 1
+ length_bits = b2 & 0x7f
+
+ self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)
+
+ def has_mask(self) -> Union[bool, int]:
+ if not self.header:
+ return False
+ return self.header[frame_buffer._HEADER_MASK_INDEX]
+
+ def has_received_length(self) -> bool:
+ return self.length is None
+
+ def recv_length(self) -> None:
+ bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
+ length_bits = bits & 0x7f
+ if length_bits == 0x7e:
+ v = self.recv_strict(2)
+ self.length = struct.unpack("!H", v)[0]
+ elif length_bits == 0x7f:
+ v = self.recv_strict(8)
+ self.length = struct.unpack("!Q", v)[0]
+ else:
+ self.length = length_bits
+
+ def has_received_mask(self) -> bool:
+ return self.mask is None
+
+ def recv_mask(self) -> None:
+ self.mask = self.recv_strict(4) if self.has_mask() else ""
+
+ def recv_frame(self) -> ABNF:
+
+ with self.lock:
+ # Header
+ if self.has_received_header():
+ self.recv_header()
+ (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header
+
+ # Frame length
+ if self.has_received_length():
+ self.recv_length()
+ length = self.length
+
+ # Mask
+ if self.has_received_mask():
+ self.recv_mask()
+ mask = self.mask
+
+ # Payload
+ payload = self.recv_strict(length)
+ if has_mask:
+ payload = ABNF.mask(mask, payload)
+
+ # Reset for next frame
+ self.clear()
+
+ frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
+ frame.validate(self.skip_utf8_validation)
+
+ return frame
+
+ 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
+ # fragmenting the heap -- the number of bytes recv() actually
+ # reads is limited by socket buffer and is relatively small,
+ # yet passing large numbers repeatedly causes lots of large
+ # buffers allocated and then shrunk, which results in
+ # fragmentation.
+ bytes_ = self.recv(min(16384, shortage))
+ self.recv_buffer.append(bytes_)
+ shortage -= len(bytes_)
+
+ unified = b"".join(self.recv_buffer)
+
+ if shortage == 0:
+ self.recv_buffer = []
+ return unified
+ else:
+ self.recv_buffer = [unified[bufsize:]]
+ return unified[:bufsize]
+
+
+class continuous_frame:
+
+ def __init__(self, fire_cont_frame: bool, skip_utf8_validation: bool) -> None:
+ self.fire_cont_frame = fire_cont_frame
+ self.skip_utf8_validation = skip_utf8_validation
+ self.cont_data = None
+ self.recving_frames = None
+
+ def validate(self, frame: ABNF) -> None:
+ if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
+ raise WebSocketProtocolException("Illegal frame")
+ if self.recving_frames and \
+ frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
+ raise WebSocketProtocolException("Illegal frame")
+
+ def add(self, frame: ABNF) -> None:
+ if self.cont_data:
+ self.cont_data[1] += frame.data
+ else:
+ if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
+ self.recving_frames = frame.opcode
+ self.cont_data = [frame.opcode, frame.data]
+
+ if frame.fin:
+ self.recving_frames = None
+
+ def is_fire(self, frame: ABNF) -> Union[bool, int]:
+ return frame.fin or self.fire_cont_frame
+
+ def extract(self, frame: ABNF) -> list:
+ data = self.cont_data
+ self.cont_data = None
+ frame.data = data[1]
+ if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data):
+ raise WebSocketPayloadException(
+ "cannot decode: " + repr(frame.data))
+
+ return [data[0], frame]
diff --git a/contrib/python/websocket-client/py3/websocket/_app.py b/contrib/python/websocket-client/py3/websocket/_app.py
new file mode 100644
index 0000000000..13f8bd5634
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_app.py
@@ -0,0 +1,558 @@
+import inspect
+import selectors
+import socket
+import sys
+import threading
+import time
+import traceback
+
+from typing import Any, Callable, Optional, Union
+
+from . import _logging
+from ._abnf import ABNF
+from ._url import parse_url
+from ._core import WebSocket, getdefaulttimeout
+from ._exceptions import *
+
+"""
+_app.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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__ = ["WebSocketApp"]
+
+RECONNECT = 0
+
+
+def setReconnect(reconnectInterval: int) -> None:
+ global RECONNECT
+ RECONNECT = reconnectInterval
+
+
+class DispatcherBase:
+ """
+ DispatcherBase
+ """
+ def __init__(self, app: Any, ping_timeout: float) -> None:
+ self.app = app
+ self.ping_timeout = ping_timeout
+
+ def timeout(self, seconds: int, callback: Callable) -> None:
+ time.sleep(seconds)
+ callback()
+
+ def reconnect(self, seconds: int, reconnector: Callable) -> None:
+ try:
+ _logging.info("reconnect() - retrying in {seconds_count} seconds [{frame_count} frames in stack]".format(
+ seconds_count=seconds, frame_count=len(inspect.stack())))
+ time.sleep(seconds)
+ reconnector(reconnecting=True)
+ except KeyboardInterrupt as e:
+ _logging.info("User exited {err}".format(err=e))
+ raise e
+
+
+class Dispatcher(DispatcherBase):
+ """
+ Dispatcher
+ """
+ def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
+ sel = selectors.DefaultSelector()
+ sel.register(self.app.sock.sock, selectors.EVENT_READ)
+ try:
+ while self.app.keep_running:
+ r = sel.select(self.ping_timeout)
+ if r:
+ if not read_callback():
+ break
+ check_callback()
+ finally:
+ sel.close()
+
+
+class SSLDispatcher(DispatcherBase):
+ """
+ SSLDispatcher
+ """
+ def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
+ sock = self.app.sock.sock
+ sel = selectors.DefaultSelector()
+ sel.register(sock, selectors.EVENT_READ)
+ try:
+ while self.app.keep_running:
+ r = self.select(sock, sel)
+ if r:
+ if not read_callback():
+ break
+ check_callback()
+ finally:
+ sel.close()
+
+ def select(self, sock, sel:selectors.DefaultSelector):
+ sock = self.app.sock.sock
+ if sock.pending():
+ return [sock,]
+
+ r = sel.select(self.ping_timeout)
+
+ if len(r) > 0:
+ return r[0][0]
+
+
+class WrappedDispatcher:
+ """
+ WrappedDispatcher
+ """
+ def __init__(self, app, ping_timeout: float, dispatcher: Dispatcher) -> None:
+ self.app = app
+ self.ping_timeout = ping_timeout
+ self.dispatcher = dispatcher
+ dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
+
+ def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None:
+ self.dispatcher.read(sock, read_callback)
+ self.ping_timeout and self.timeout(self.ping_timeout, check_callback)
+
+ def timeout(self, seconds: int, callback: Callable) -> None:
+ self.dispatcher.timeout(seconds, callback)
+
+ def reconnect(self, seconds: int, reconnector: Callable) -> None:
+ self.timeout(seconds, reconnector)
+
+
+class WebSocketApp:
+ """
+ Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
+ """
+
+ def __init__(self, url: str, header: Union[list, dict, Callable] = None,
+ on_open: Callable = None, on_message: Callable = None, on_error: Callable = None,
+ on_close: Callable = None, on_ping: Callable = None, on_pong: Callable = None,
+ on_cont_message: Callable = None,
+ keep_running: bool = True, get_mask_key: Callable = None, cookie: str = None,
+ subprotocols: list = None,
+ on_data: Callable = None,
+ socket: socket.socket = None) -> None:
+ """
+ WebSocketApp initialization
+
+ Parameters
+ ----------
+ url: str
+ Websocket url.
+ header: list or dict or Callable
+ Custom header for websocket handshake.
+ If the parameter is a callable object, it is called just before the connection attempt.
+ The returned dict or list is used as custom header value.
+ This could be useful in order to properly setup timestamp dependent headers.
+ on_open: function
+ Callback object which is called at opening websocket.
+ on_open has one argument.
+ The 1st argument is this class object.
+ on_message: function
+ Callback object which is called when received data.
+ on_message has 2 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 data received from the server.
+ on_error: function
+ Callback object which is called when we get error.
+ on_error has 2 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is exception object.
+ on_close: function
+ Callback object which is called when connection is closed.
+ on_close has 3 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is close_status_code.
+ The 3rd argument is close_msg.
+ on_cont_message: function
+ Callback object which is called when a continuation
+ frame is received.
+ on_cont_message has 3 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ The 3rd argument is continue flag. if 0, the data continue
+ to next frame data
+ on_data: function
+ Callback object which is called when a message received.
+ This is called before on_message or on_cont_message,
+ and then on_message or on_cont_message is called.
+ on_data has 4 argument.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
+ The 4th argument is continue flag. If 0, the data continue
+ keep_running: bool
+ This parameter is obsolete and ignored.
+ get_mask_key: function
+ A callable function to get new mask keys, see the
+ WebSocket.set_mask_key's docstring for more information.
+ cookie: str
+ 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 []
+ self.cookie = cookie
+
+ self.on_open = on_open
+ self.on_message = on_message
+ self.on_data = on_data
+ self.on_error = on_error
+ self.on_close = on_close
+ self.on_ping = on_ping
+ self.on_pong = on_pong
+ self.on_cont_message = on_cont_message
+ self.keep_running = False
+ self.get_mask_key = get_mask_key
+ self.sock = None
+ self.last_ping_tm = 0
+ self.last_pong_tm = 0
+ self.ping_thread = None
+ self.stop_ping = None
+ self.ping_interval = 0
+ self.ping_timeout = None
+ self.ping_payload = ""
+ self.subprotocols = subprotocols
+ self.prepared_socket = socket
+ self.has_errored = False
+ self.has_done_teardown = False
+ self.has_done_teardown_lock = threading.Lock()
+
+ def send(self, data: str, opcode: int = ABNF.OPCODE_TEXT) -> None:
+ """
+ send message
+
+ Parameters
+ ----------
+ data: str
+ Message to send. If you set opcode to OPCODE_TEXT,
+ data must be utf-8 string or unicode.
+ opcode: int
+ Operation code of data. Default is OPCODE_TEXT.
+ """
+
+ if not self.sock or self.sock.send(data, opcode) == 0:
+ raise WebSocketConnectionClosedException(
+ "Connection is already closed.")
+
+ def close(self, **kwargs) -> None:
+ """
+ Close websocket connection.
+ """
+ self.keep_running = False
+ if self.sock:
+ self.sock.close(**kwargs)
+ self.sock = None
+
+ def _start_ping_thread(self) -> None:
+ self.last_ping_tm = self.last_pong_tm = 0
+ self.stop_ping = threading.Event()
+ self.ping_thread = threading.Thread(target=self._send_ping)
+ self.ping_thread.daemon = True
+ self.ping_thread.start()
+
+ def _stop_ping_thread(self) -> None:
+ if self.stop_ping:
+ self.stop_ping.set()
+ if self.ping_thread and self.ping_thread.is_alive():
+ self.ping_thread.join(3)
+ self.last_ping_tm = self.last_pong_tm = 0
+
+ def _send_ping(self) -> None:
+ if self.stop_ping.wait(self.ping_interval) or self.keep_running is False:
+ return
+ while not self.stop_ping.wait(self.ping_interval) and self.keep_running is True:
+ if self.sock:
+ self.last_ping_tm = time.time()
+ try:
+ _logging.debug("Sending ping")
+ self.sock.ping(self.ping_payload)
+ except Exception as e:
+ _logging.debug("Failed to send ping: {err}".format(err=e))
+
+ def run_forever(self, sockopt: tuple = None, sslopt: dict = None,
+ ping_interval: float = 0, ping_timeout: Optional[float] = None,
+ ping_payload: str = "",
+ http_proxy_host: str = None, http_proxy_port: Union[int, str] = None,
+ http_no_proxy: list = None, http_proxy_auth: tuple = None,
+ http_proxy_timeout: float = None,
+ skip_utf8_validation: bool = False,
+ host: str = None, origin: str = None, dispatcher: Dispatcher = None,
+ suppress_origin: bool = False, proxy_type: str = None, reconnect: int = None) -> bool:
+ """
+ Run event loop for WebSocket framework.
+
+ This loop is an infinite loop and is alive while websocket is available.
+
+ Parameters
+ ----------
+ sockopt: tuple
+ Values for socket.setsockopt.
+ sockopt must be tuple
+ and each element is argument of sock.setsockopt.
+ sslopt: dict
+ Optional dict object for ssl socket option.
+ ping_interval: int or float
+ Automatically send "ping" command
+ every specified period (in seconds).
+ If set to 0, no ping is sent periodically.
+ ping_timeout: int or float
+ Timeout (in seconds) if the pong message is not received.
+ ping_payload: str
+ Payload message to send with each ping.
+ http_proxy_host: str
+ HTTP proxy host name.
+ http_proxy_port: int or str
+ 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
+ update host header.
+ origin: str
+ update origin header.
+ dispatcher: Dispatcher object
+ 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 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:
+ raise WebSocketException("Ensure ping_interval >= 0")
+ if ping_timeout and ping_interval and ping_interval <= ping_timeout:
+ raise WebSocketException("Ensure ping_interval > ping_timeout")
+ if not sockopt:
+ sockopt = []
+ if not sslopt:
+ sslopt = {}
+ if self.sock:
+ raise WebSocketException("socket is already opened")
+
+ self.ping_interval = ping_interval
+ self.ping_timeout = ping_timeout
+ self.ping_payload = ping_payload
+ self.keep_running = True
+
+ def teardown(close_frame: ABNF = None):
+ """
+ Tears down the connection.
+
+ Parameters
+ ----------
+ close_frame: ABNF frame
+ If close_frame is set, the on_close handler is invoked
+ with the statusCode and reason from the provided frame.
+ """
+
+ # teardown() is called in many code paths to ensure resources are cleaned up and on_close is fired.
+ # To ensure the work is only done once, we use this bool and lock.
+ with self.has_done_teardown_lock:
+ if self.has_done_teardown:
+ return
+ self.has_done_teardown = True
+
+ self._stop_ping_thread()
+ self.keep_running = False
+ if self.sock:
+ self.sock.close()
+ close_status_code, close_reason = self._get_close_args(
+ close_frame if close_frame else None)
+ self.sock = None
+
+ # Finally call the callback AFTER all teardown is complete
+ self._callback(self.on_close, close_status_code, close_reason)
+
+ def setSock(reconnecting: bool = False) -> None:
+ if reconnecting and self.sock:
+ self.sock.shutdown()
+
+ 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())
+ try:
+
+ header = self.header() if callable(self.header) else self.header
+
+ self.sock.connect(
+ self.url, header=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)
+
+ _logging.info("Websocket connected")
+
+ if self.ping_interval:
+ self._start_ping_thread()
+
+ self._callback(self.on_open)
+
+ dispatcher.read(self.sock.sock, read, check)
+ except (WebSocketConnectionClosedException, ConnectionRefusedError, KeyboardInterrupt, SystemExit, Exception) as e:
+ handleDisconnect(e, reconnecting)
+
+ def read() -> bool:
+ if not self.keep_running:
+ return teardown()
+
+ try:
+ op_code, frame = self.sock.recv_data_frame(True)
+ except (WebSocketConnectionClosedException, KeyboardInterrupt) as e:
+ if custom_dispatcher:
+ return handleDisconnect(e)
+ else:
+ 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() -> bool:
+ if (self.ping_timeout):
+ has_timeout_expired = time.time() - self.last_ping_tm > self.ping_timeout
+ has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
+ has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > self.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: Exception, reconnecting: bool = False) -> bool:
+ self.has_errored = True
+ self._stop_ping_thread()
+ if not reconnecting:
+ self._callback(self.on_error, e)
+
+ if isinstance(e, (KeyboardInterrupt, SystemExit)):
+ teardown()
+ # Propagate further
+ raise
+
+ if reconnect:
+ _logging.info("{err} - reconnect".format(err=e))
+ if custom_dispatcher:
+ _logging.debug("Calling custom dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack())))
+ dispatcher.reconnect(reconnect, setSock)
+ else:
+ _logging.error("{err} - goodbye".format(err=e))
+ teardown()
+
+ custom_dispatcher = bool(dispatcher)
+ dispatcher = self.create_dispatcher(ping_timeout, dispatcher, parse_url(self.url)[3])
+
+ try:
+ setSock()
+ if not custom_dispatcher and reconnect:
+ while self.keep_running:
+ _logging.debug("Calling dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack())))
+ dispatcher.reconnect(reconnect, setSock)
+ except (KeyboardInterrupt, Exception) as e:
+ _logging.info("tearing down on exception {err}".format(err=e))
+ teardown()
+ finally:
+ if not custom_dispatcher:
+ # Ensure teardown was called before returning from run_forever
+ teardown()
+
+ return self.has_errored
+
+ def create_dispatcher(self, ping_timeout: int, dispatcher: Dispatcher = None, is_ssl: bool = False) -> DispatcherBase:
+ if dispatcher: # If custom dispatcher is set, use WrappedDispatcher
+ return WrappedDispatcher(self, ping_timeout, dispatcher)
+ timeout = ping_timeout or 10
+ if is_ssl:
+ return SSLDispatcher(self, timeout)
+
+ return Dispatcher(self, timeout)
+
+ def _get_close_args(self, close_frame: ABNF) -> list:
+ """
+ _get_close_args extracts the close code and reason from the close body
+ if it exists (RFC6455 says WebSocket Connection Close Code is optional)
+ """
+ # Need to catch the case where close_frame is None
+ # Otherwise the following if statement causes an error
+ if not self.on_close or not close_frame:
+ return [None, None]
+
+ # Extract close frame status code
+ if close_frame.data and len(close_frame.data) >= 2:
+ close_status_code = 256 * close_frame.data[0] + close_frame.data[1]
+ reason = close_frame.data[2:].decode('utf-8')
+ return [close_status_code, reason]
+ else:
+ # Most likely reached this because len(close_frame_data.data) < 2
+ return [None, None]
+
+ def _callback(self, callback, *args) -> None:
+ if callback:
+ try:
+ callback(self, *args)
+
+ except Exception as e:
+ _logging.error("error from callback {callback}: {err}".format(callback=callback, err=e))
+ if self.on_error:
+ self.on_error(self, e)
diff --git a/contrib/python/websocket-client/py3/websocket/_cookiejar.py b/contrib/python/websocket-client/py3/websocket/_cookiejar.py
new file mode 100644
index 0000000000..bf907d6bdb
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_cookiejar.py
@@ -0,0 +1,66 @@
+import http.cookies
+
+from typing import Optional
+
+"""
+_cookiejar.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+
+class SimpleCookieJar:
+ def __init__(self) -> None:
+ self.jar = dict()
+
+ def add(self, set_cookie: Optional[str]) -> None:
+ if set_cookie:
+ simpleCookie = http.cookies.SimpleCookie(set_cookie)
+
+ for k, v in simpleCookie.items():
+ domain = v.get("domain")
+ if domain:
+ if not domain.startswith("."):
+ domain = "." + domain
+ cookie = self.jar.get(domain) if self.jar.get(domain) else http.cookies.SimpleCookie()
+ cookie.update(simpleCookie)
+ self.jar[domain.lower()] = cookie
+
+ def set(self, set_cookie: str) -> None:
+ if set_cookie:
+ simpleCookie = http.cookies.SimpleCookie(set_cookie)
+
+ for k, v in simpleCookie.items():
+ domain = v.get("domain")
+ if domain:
+ if not domain.startswith("."):
+ domain = "." + domain
+ self.jar[domain.lower()] = simpleCookie
+
+ def get(self, host: str) -> str:
+ if not host:
+ return ""
+
+ cookies = []
+ for domain, simpleCookie in self.jar.items():
+ host = host.lower()
+ if host.endswith(domain) or host == domain[1:]:
+ cookies.append(self.jar.get(domain))
+
+ return "; ".join(filter(
+ None, sorted(
+ ["%s=%s" % (k, v.value) for cookie in filter(None, cookies) for k, v in cookie.items()]
+ )))
diff --git a/contrib/python/websocket-client/py3/websocket/_core.py b/contrib/python/websocket-client/py3/websocket/_core.py
new file mode 100644
index 0000000000..fea2b6d49c
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_core.py
@@ -0,0 +1,611 @@
+import socket
+import struct
+import threading
+import time
+
+from typing import Optional, Union
+
+# 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 2023 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']
+
+
+class WebSocket:
+ """
+ Low level WebSocket interface.
+
+ This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_
+
+ We can connect to the websocket server and send/receive data.
+ The following example is an echo client.
+
+ >>> import websocket
+ >>> ws = websocket.WebSocket()
+ >>> 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()
+
+ Parameters
+ ----------
+ get_mask_key: func
+ A callable function to get new mask keys, see the
+ WebSocket.set_mask_key's docstring for more information.
+ sockopt: tuple
+ Values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setsockopt.
+ sslopt: dict
+ Optional dict object for ssl socket options. See FAQ for details.
+ fire_cont_frame: bool
+ Fire recv event for each cont frame. Default is False.
+ enable_multithread: bool
+ If set to True, lock send method.
+ skip_utf8_validation: bool
+ Skip utf8 validation.
+ """
+
+ def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
+ fire_cont_frame: bool = False, enable_multithread: bool = True,
+ skip_utf8_validation: bool = False, **_):
+ """
+ Initialize WebSocket object.
+
+ Parameters
+ ----------
+ sslopt: dict
+ Optional dict object for ssl socket options. See FAQ for details.
+ """
+ self.sock_opt = sock_opt(sockopt, sslopt)
+ self.handshake_response = None
+ self.sock = None
+
+ self.connected = False
+ self.get_mask_key = get_mask_key
+ # These buffer over the build-up of a single frame.
+ self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
+ self.cont_frame = continuous_frame(
+ fire_cont_frame, skip_utf8_validation)
+
+ if enable_multithread:
+ self.lock = threading.Lock()
+ self.readlock = threading.Lock()
+ else:
+ self.lock = NoLock()
+ self.readlock = NoLock()
+
+ def __iter__(self):
+ """
+ Allow iteration over websocket, implying sequential `recv` executions.
+ """
+ while True:
+ yield self.recv()
+
+ def __next__(self):
+ return self.recv()
+
+ def next(self):
+ return self.__next__()
+
+ def fileno(self):
+ return self.sock.fileno()
+
+ def set_mask_key(self, func):
+ """
+ Set function to create mask key. You can customize mask key generator.
+ Mainly, this is for testing purpose.
+
+ Parameters
+ ----------
+ func: func
+ callable object. the func takes 1 argument as integer.
+ The argument means length of mask key.
+ This func must return string(byte array),
+ which length is argument specified.
+ """
+ self.get_mask_key = func
+
+ def gettimeout(self) -> float:
+ """
+ Get the websocket timeout (in seconds) as an int or float
+
+ Returns
+ ----------
+ timeout: int or float
+ returns timeout value (in seconds). This value could be either float/integer.
+ """
+ return self.sock_opt.timeout
+
+ def settimeout(self, timeout: Optional[float]):
+ """
+ Set the timeout to the websocket.
+
+ Parameters
+ ----------
+ timeout: int or float
+ timeout time (in seconds). This value could be either float/integer.
+ """
+ self.sock_opt.timeout = timeout
+ if self.sock:
+ self.sock.settimeout(timeout)
+
+ timeout = property(gettimeout, settimeout)
+
+ def getsubprotocol(self):
+ """
+ Get subprotocol
+ """
+ if self.handshake_response:
+ return self.handshake_response.subprotocol
+ else:
+ return None
+
+ subprotocol = property(getsubprotocol)
+
+ def getstatus(self):
+ """
+ Get handshake status
+ """
+ if self.handshake_response:
+ return self.handshake_response.status
+ else:
+ return None
+
+ status = property(getstatus)
+
+ def getheaders(self):
+ """
+ Get handshake response header
+ """
+ if self.handshake_response:
+ return self.handshake_response.headers
+ else:
+ return None
+
+ def is_ssl(self):
+ try:
+ return isinstance(self.sock, ssl.SSLSocket)
+ except:
+ return False
+
+ headers = property(getheaders)
+
+ def connect(self, url, **options):
+ """
+ Connect to url. url is websocket url scheme.
+ ie. ws://host:port/resource
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> ws = WebSocket()
+ >>> ws.connect("ws://echo.websocket.events",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+ Parameters
+ ----------
+ header: list or dict
+ Custom http header list or dict.
+ cookie: str
+ Cookie value.
+ origin: str
+ Custom origin url.
+ connection: str
+ Custom connection header value.
+ Default value "Upgrade" set in _handshake.py
+ suppress_origin: bool
+ Suppress outputting origin header.
+ host: str
+ Custom host header string.
+ timeout: int or float
+ Socket timeout time. This value is an integer or float.
+ If you set None for this value, it means "use default_timeout value"
+ http_proxy_host: str
+ HTTP proxy host name.
+ http_proxy_port: str or int
+ HTTP proxy port. Default is 80.
+ http_no_proxy: list
+ 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
+ List of available subprotocols. Default is None.
+ socket: socket
+ Pre-initialized stream socket.
+ """
+ self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
+ self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
+ options.pop('socket', None))
+
+ try:
+ 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, url, *addrs, **options)
+ self.connected = True
+ except:
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ raise
+
+ def send(self, payload: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> int:
+ """
+ Send the data as string.
+
+ Parameters
+ ----------
+ payload: str
+ Payload must be utf-8 string or unicode,
+ If the opcode is OPCODE_TEXT.
+ Otherwise, it must be string(byte array).
+ opcode: int
+ Operation code (opcode) to send.
+ """
+
+ frame = ABNF.create_frame(payload, opcode)
+ return self.send_frame(frame)
+
+ def send_frame(self, frame) -> int:
+ """
+ Send the data frame.
+
+ >>> 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)
+ >>> ws.send_frame(frame)
+ >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
+ >>> ws.send_frame(frame)
+
+ Parameters
+ ----------
+ frame: ABNF frame
+ frame data created by ABNF.create_frame
+ """
+ if self.get_mask_key:
+ frame.get_mask_key = self.get_mask_key
+ data = frame.format()
+ length = len(data)
+ if (isEnabledForTrace()):
+ trace("++Sent raw: " + repr(data))
+ trace("++Sent decoded: " + frame.__str__())
+ with self.lock:
+ while data:
+ l = self._send(data)
+ data = data[l:]
+
+ return length
+
+ def send_binary(self, payload: bytes) -> int:
+ """
+ Send a binary message (OPCODE_BINARY).
+
+ Parameters
+ ----------
+ payload: bytes
+ payload of message to send.
+ """
+ return self.send(payload, ABNF.OPCODE_BINARY)
+
+ def ping(self, payload: Union[str, bytes] = ""):
+ """
+ Send ping data.
+
+ Parameters
+ ----------
+ payload: str
+ data payload to send server.
+ """
+ if isinstance(payload, str):
+ payload = payload.encode("utf-8")
+ self.send(payload, ABNF.OPCODE_PING)
+
+ def pong(self, payload: Union[str, bytes] = ""):
+ """
+ Send pong data.
+
+ Parameters
+ ----------
+ payload: str
+ data payload to send server.
+ """
+ if isinstance(payload, str):
+ payload = payload.encode("utf-8")
+ self.send(payload, ABNF.OPCODE_PONG)
+
+ def recv(self) -> Union[str, bytes]:
+ """
+ Receive string data(byte array) from the server.
+
+ Returns
+ ----------
+ data: string (byte array) value.
+ """
+ with self.readlock:
+ opcode, data = self.recv_data()
+ if opcode == ABNF.OPCODE_TEXT:
+ return data.decode("utf-8")
+ elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY:
+ return data
+ else:
+ return ''
+
+ def recv_data(self, control_frame: bool = False) -> tuple:
+ """
+ Receive data with operation code.
+
+ Parameters
+ ----------
+ control_frame: bool
+ a boolean flag indicating whether to return control frame
+ data, defaults to False
+
+ Returns
+ -------
+ opcode, frame.data: tuple
+ tuple of operation code and string(byte array) value.
+ """
+ opcode, frame = self.recv_data_frame(control_frame)
+ return opcode, frame.data
+
+ def recv_data_frame(self, control_frame: bool = False):
+ """
+ Receive data with operation code.
+
+ If a valid ping message is received, a pong response is sent.
+
+ Parameters
+ ----------
+ control_frame: bool
+ a boolean flag indicating whether to return control frame
+ data, defaults to False
+
+ Returns
+ -------
+ frame.opcode, frame: tuple
+ tuple of operation code and string(byte array) value.
+ """
+ while True:
+ frame = self.recv_frame()
+ if (isEnabledForTrace()):
+ trace("++Rcv raw: " + repr(frame.format()))
+ trace("++Rcv decoded: " + frame.__str__())
+ if not frame:
+ # handle error:
+ # 'NoneType' object has no attribute 'opcode'
+ raise WebSocketProtocolException(
+ "Not a valid frame {frame}".format(frame=frame))
+ elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
+ self.cont_frame.validate(frame)
+ self.cont_frame.add(frame)
+
+ if self.cont_frame.is_fire(frame):
+ return self.cont_frame.extract(frame)
+
+ elif frame.opcode == ABNF.OPCODE_CLOSE:
+ self.send_close()
+ return frame.opcode, frame
+ elif frame.opcode == ABNF.OPCODE_PING:
+ if len(frame.data) < 126:
+ self.pong(frame.data)
+ else:
+ raise WebSocketProtocolException(
+ "Ping message is too long")
+ if control_frame:
+ return frame.opcode, frame
+ elif frame.opcode == ABNF.OPCODE_PONG:
+ if control_frame:
+ return frame.opcode, frame
+
+ def recv_frame(self):
+ """
+ Receive data as frame from server.
+
+ Returns
+ -------
+ self.frame_buffer.recv_frame(): ABNF frame object
+ """
+ return self.frame_buffer.recv_frame()
+
+ def send_close(self, status: int = STATUS_NORMAL, reason: bytes = b""):
+ """
+ Send close data to the server.
+
+ Parameters
+ ----------
+ status: int
+ Status code to send. See STATUS_XXX.
+ reason: str or bytes
+ The reason to close. This must be string or UTF-8 bytes.
+ """
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+ self.connected = False
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+
+ def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: float = 3):
+ """
+ Close Websocket object
+
+ Parameters
+ ----------
+ status: int
+ Status code to send. See VALID_CLOSE_STATUS in ABNF.
+ reason: bytes
+ The reason to close in UTF-8.
+ timeout: int or float
+ Timeout until receive a close frame.
+ If None, it will wait forever until receive a close frame.
+ """
+ if self.connected:
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+
+ try:
+ self.connected = False
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+ sock_timeout = self.sock.gettimeout()
+ self.sock.settimeout(timeout)
+ start_time = time.time()
+ while timeout is None or time.time() - start_time < timeout:
+ try:
+ frame = self.recv_frame()
+ if frame.opcode != ABNF.OPCODE_CLOSE:
+ continue
+ if isEnabledForError():
+ recv_status = struct.unpack("!H", frame.data[0:2])[0]
+ if recv_status >= 3000 and recv_status <= 4999:
+ debug("close status: " + repr(recv_status))
+ elif recv_status != STATUS_NORMAL:
+ error("close status: " + repr(recv_status))
+ break
+ except:
+ break
+ self.sock.settimeout(sock_timeout)
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+
+ self.shutdown()
+
+ def abort(self):
+ """
+ Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
+ """
+ if self.connected:
+ self.sock.shutdown(socket.SHUT_RDWR)
+
+ def shutdown(self):
+ """
+ close socket, immediately.
+ """
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ self.connected = False
+
+ def _send(self, data: Union[str, bytes]):
+ return send(self.sock, data)
+
+ def _recv(self, bufsize):
+ try:
+ return recv(self.sock, bufsize)
+ except WebSocketConnectionClosedException:
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ self.connected = False
+ raise
+
+
+def create_connection(url: str, timeout=None, class_=WebSocket, **options):
+ """
+ Connect to url and return websocket object.
+
+ Connect to url and return the WebSocket object.
+ Passing optional timeout parameter will set the timeout on the socket.
+ If no timeout is supplied,
+ the global default timeout setting returned by getdefaulttimeout() is used.
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> conn = create_connection("ws://echo.websocket.events",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+ Parameters
+ ----------
+ class_: class
+ class to instantiate when creating the connection. It has to implement
+ settimeout and connect. It's __init__ should be compatible with
+ WebSocket.__init__, i.e. accept all of it's kwargs.
+ header: list or dict
+ custom http header list or dict.
+ cookie: str
+ Cookie value.
+ origin: str
+ custom origin url.
+ suppress_origin: bool
+ suppress outputting origin header.
+ host: str
+ custom host header string.
+ timeout: int or float
+ socket timeout time. This value could be either float/integer.
+ If set to None, it uses the default_timeout value.
+ http_proxy_host: str
+ HTTP proxy host name.
+ http_proxy_port: str or int
+ HTTP proxy port. If not set, set to 80.
+ http_no_proxy: list
+ 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
+ Number of redirects to follow.
+ sockopt: tuple
+ Values for socket.setsockopt.
+ sockopt must be a tuple and each element is an argument of sock.setsockopt.
+ sslopt: dict
+ Optional dict object for ssl socket options. See FAQ for details.
+ subprotocols: list
+ List of available subprotocols. Default is None.
+ skip_utf8_validation: bool
+ Skip utf8 validation.
+ socket: socket
+ Pre-initialized stream socket.
+ """
+ sockopt = options.pop("sockopt", [])
+ sslopt = options.pop("sslopt", {})
+ fire_cont_frame = options.pop("fire_cont_frame", False)
+ enable_multithread = options.pop("enable_multithread", True)
+ skip_utf8_validation = options.pop("skip_utf8_validation", False)
+ websock = class_(sockopt=sockopt, sslopt=sslopt,
+ fire_cont_frame=fire_cont_frame,
+ enable_multithread=enable_multithread,
+ skip_utf8_validation=skip_utf8_validation, **options)
+ websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
+ websock.connect(url, **options)
+ return websock
diff --git a/contrib/python/websocket-client/py3/websocket/_exceptions.py b/contrib/python/websocket-client/py3/websocket/_exceptions.py
new file mode 100644
index 0000000000..48f40a0724
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_exceptions.py
@@ -0,0 +1,80 @@
+"""
+_exceptions.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+
+class WebSocketException(Exception):
+ """
+ WebSocket exception class.
+ """
+ pass
+
+
+class WebSocketProtocolException(WebSocketException):
+ """
+ If the WebSocket protocol is invalid, this exception will be raised.
+ """
+ pass
+
+
+class WebSocketPayloadException(WebSocketException):
+ """
+ If the WebSocket payload is invalid, this exception will be raised.
+ """
+ pass
+
+
+class WebSocketConnectionClosedException(WebSocketException):
+ """
+ If remote host closed the connection or some network error happened,
+ this exception will be raised.
+ """
+ pass
+
+
+class WebSocketTimeoutException(WebSocketException):
+ """
+ WebSocketTimeoutException will be raised at socket timeout during read/write data.
+ """
+ pass
+
+
+class WebSocketProxyException(WebSocketException):
+ """
+ WebSocketProxyException will be raised when proxy error occurred.
+ """
+ pass
+
+
+class WebSocketBadStatusException(WebSocketException):
+ """
+ WebSocketBadStatusException will be raised when we get bad handshake status code.
+ """
+
+ def __init__(self, message: str, status_code: int, status_message=None, resp_headers=None, resp_body=None):
+ super().__init__(message)
+ self.status_code = status_code
+ self.resp_headers = resp_headers
+ self.resp_body = resp_body
+
+
+class WebSocketAddressException(WebSocketException):
+ """
+ If the websocket address info cannot be found, this exception will be raised.
+ """
+ pass
diff --git a/contrib/python/websocket-client/py3/websocket/_handshake.py b/contrib/python/websocket-client/py3/websocket/_handshake.py
new file mode 100644
index 0000000000..a94d3030c3
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_handshake.py
@@ -0,0 +1,197 @@
+"""
+_handshake.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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 hashlib
+import hmac
+import os
+from base64 import encodebytes as base64encode
+from http import client as HTTPStatus
+from ._cookiejar import SimpleCookieJar
+from ._exceptions import *
+from ._http import *
+from ._logging import *
+from ._socket import *
+
+__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"]
+
+# websocket supported version.
+VERSION = 13
+
+SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER, HTTPStatus.TEMPORARY_REDIRECT, HTTPStatus.PERMANENT_REDIRECT)
+SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,)
+
+CookieJar = SimpleCookieJar()
+
+
+class handshake_response:
+
+ def __init__(self, status: int, headers: dict, subprotocol):
+ self.status = status
+ self.headers = headers
+ self.subprotocol = subprotocol
+ CookieJar.add(headers.get("set-cookie"))
+
+
+def handshake(sock, url: str, hostname: str, port: int, resource: str, **options):
+ headers, key = _get_handshake_headers(resource, url, hostname, port, options)
+
+ header_str = "\r\n".join(headers)
+ send(sock, header_str)
+ dump("request header", header_str)
+
+ status, resp = _get_resp_headers(sock)
+ if status in SUPPORTED_REDIRECT_STATUSES:
+ return handshake_response(status, resp, None)
+ success, subproto = _validate(resp, key, options.get("subprotocols"))
+ if not success:
+ raise WebSocketException("Invalid WebSocket Header")
+
+ return handshake_response(status, resp, subproto)
+
+
+def _pack_hostname(hostname: str) -> str:
+ # IPv6 address
+ if ':' in hostname:
+ return '[' + hostname + ']'
+
+ return hostname
+
+
+def _get_handshake_headers(resource: str, url: str, host: str, port: int, options: dict):
+ headers = [
+ "GET {resource} HTTP/1.1".format(resource=resource),
+ "Upgrade: websocket"
+ ]
+ if port == 80 or port == 443:
+ hostport = _pack_hostname(host)
+ else:
+ hostport = "{h}:{p}".format(h=_pack_hostname(host), p=port)
+ if options.get("host"):
+ headers.append("Host: {h}".format(h=options["host"]))
+ else:
+ headers.append("Host: {hp}".format(hp=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 not options.get("suppress_origin"):
+ if "origin" in options and options["origin"] is not None:
+ headers.append("Origin: {origin}".format(origin=options["origin"]))
+ elif scheme == "wss":
+ headers.append("Origin: https://{hp}".format(hp=hostport))
+ else:
+ headers.append("Origin: http://{hp}".format(hp=hostport))
+
+ key = _create_sec_websocket_key()
+
+ # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
+ if not options.get('header') or 'Sec-WebSocket-Key' not in options['header']:
+ headers.append("Sec-WebSocket-Key: {key}".format(key=key))
+ else:
+ key = options['header']['Sec-WebSocket-Key']
+
+ if not options.get('header') or 'Sec-WebSocket-Version' not in options['header']:
+ headers.append("Sec-WebSocket-Version: {version}".format(version=VERSION))
+
+ if not options.get('connection'):
+ headers.append('Connection: Upgrade')
+ else:
+ headers.append(options['connection'])
+
+ subprotocols = options.get("subprotocols")
+ if subprotocols:
+ headers.append("Sec-WebSocket-Protocol: {protocols}".format(protocols=",".join(subprotocols)))
+
+ header = options.get("header")
+ if header:
+ if isinstance(header, dict):
+ header = [
+ ": ".join([k, v])
+ for k, v in header.items()
+ if v is not None
+ ]
+ headers.extend(header)
+
+ server_cookie = CookieJar.get(host)
+ client_cookie = options.get("cookie", None)
+
+ cookie = "; ".join(filter(None, [server_cookie, client_cookie]))
+
+ if cookie:
+ headers.append("Cookie: {cookie}".format(cookie=cookie))
+
+ headers.extend(("", ""))
+ return headers, key
+
+
+def _get_resp_headers(sock, success_statuses: tuple = SUCCESS_STATUSES) -> tuple:
+ status, resp_headers, status_message = read_headers(sock)
+ if status not in success_statuses:
+ content_len = resp_headers.get('content-length')
+ if content_len:
+ response_body = sock.recv(int(content_len)) # read the body of the HTTP error message response and include it in the exception
+ else:
+ response_body = None
+ raise WebSocketBadStatusException("Handshake status {status} {message} -+-+- {headers} -+-+- {body}".format(status=status, message=status_message, headers=resp_headers, body=response_body), status, status_message, resp_headers, response_body)
+ return status, resp_headers
+
+
+_HEADERS_TO_CHECK = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+}
+
+
+def _validate(headers, key: str, subprotocols):
+ subproto = None
+ for k, v in _HEADERS_TO_CHECK.items():
+ r = headers.get(k, None)
+ if not r:
+ return False, None
+ r = [x.strip().lower() for x in r.split(',')]
+ if v not in r:
+ return False, None
+
+ if subprotocols:
+ subproto = headers.get("sec-websocket-protocol", None)
+ if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]:
+ error("Invalid subprotocol: " + str(subprotocols))
+ return False, None
+ subproto = subproto.lower()
+
+ result = headers.get("sec-websocket-accept", None)
+ if not result:
+ return False, None
+ result = result.lower()
+
+ if isinstance(result, str):
+ result = result.encode('utf-8')
+
+ value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8')
+ hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()
+ success = hmac.compare_digest(hashed, result)
+
+ if success:
+ return True, subproto
+ else:
+ return False, None
+
+
+def _create_sec_websocket_key() -> str:
+ randomness = os.urandom(16)
+ return base64encode(randomness).decode('utf-8').strip()
diff --git a/contrib/python/websocket-client/py3/websocket/_http.py b/contrib/python/websocket-client/py3/websocket/_http.py
new file mode 100644
index 0000000000..13183b2034
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_http.py
@@ -0,0 +1,340 @@
+"""
+_http.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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 errno
+import os
+import socket
+
+from ._exceptions import *
+from ._logging import *
+from ._socket import *
+from ._ssl_compat import *
+from ._url import *
+
+from base64 import encodebytes as base64encode
+
+__all__ = ["proxy_info", "connect", "read_headers"]
+
+try:
+ from python_socks.sync import Proxy
+ from python_socks._errors import *
+ from python_socks._types import ProxyType
+ HAVE_PYTHON_SOCKS = True
+except:
+ HAVE_PYTHON_SOCKS = False
+
+ class ProxyError(Exception):
+ pass
+
+ class ProxyTimeoutError(Exception):
+ pass
+
+ class ProxyConnectionError(Exception):
+ pass
+
+
+class proxy_info:
+
+ def __init__(self, **options):
+ self.proxy_host = options.get("http_proxy_host", None)
+ if self.proxy_host:
+ self.proxy_port = options.get("http_proxy_port", 0)
+ self.auth = options.get("http_proxy_auth", None)
+ 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("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:
+ self.proxy_port = 0
+ self.auth = None
+ self.no_proxy = None
+ self.proxy_protocol = "http"
+
+
+def _start_proxied_socket(url: str, options, proxy):
+ if not HAVE_PYTHON_SOCKS:
+ raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available")
+
+ hostname, port, resource, is_secure = parse_url(url)
+
+ if proxy.proxy_protocol == "socks5":
+ rdns = False
+ proxy_type = ProxyType.SOCKS5
+ if proxy.proxy_protocol == "socks4":
+ rdns = False
+ proxy_type = ProxyType.SOCKS4
+ # socks5h and socks4a send DNS through proxy
+ if proxy.proxy_protocol == "socks5h":
+ rdns = True
+ proxy_type = ProxyType.SOCKS5
+ if proxy.proxy_protocol == "socks4a":
+ rdns = True
+ proxy_type = ProxyType.SOCKS4
+
+ ws_proxy = Proxy.create(
+ proxy_type=proxy_type,
+ host=proxy.proxy_host,
+ port=int(proxy.proxy_port),
+ username=proxy.auth[0] if proxy.auth else None,
+ password=proxy.auth[1] if proxy.auth else None,
+ rdns=rdns)
+
+ sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout)
+
+ if is_secure and HAVE_SSL:
+ sock = _ssl_socket(sock, options.sslopt, hostname)
+ elif is_secure:
+ raise WebSocketException("SSL not available.")
+
+ return sock, (hostname, port, resource)
+
+
+def connect(url: str, options, proxy, socket):
+ # Use _start_proxied_socket() only for socks4 or socks5 proxy
+ # Use _tunnel() for http proxy
+ # TODO: Use python-socks for http protocol also, to standardize flow
+ if proxy.proxy_host and not socket and not (proxy.proxy_protocol == "http"):
+ return _start_proxied_socket(url, options, proxy)
+
+ hostname, port_from_url, resource, is_secure = parse_url(url)
+
+ if socket:
+ return socket, (hostname, port_from_url, resource)
+
+ addrinfo_list, need_tunnel, auth = _get_addrinfo_list(
+ hostname, port_from_url, is_secure, proxy)
+ if not addrinfo_list:
+ raise WebSocketException(
+ "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_from_url, auth)
+
+ if is_secure:
+ if HAVE_SSL:
+ sock = _ssl_socket(sock, options.sslopt, hostname)
+ else:
+ raise WebSocketException("SSL not available.")
+
+ return sock, (hostname, port_from_url, resource)
+ except:
+ if sock:
+ sock.close()
+ raise
+
+
+def _get_addrinfo_list(hostname, port, is_secure, proxy):
+ phost, pport, pauth = get_proxy_info(
+ hostname, is_secure, proxy.proxy_host, proxy.proxy_port, proxy.auth, proxy.no_proxy)
+ try:
+ # when running on windows 10, getaddrinfo without socktype returns a socktype 0.
+ # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0`
+ # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM.
+ if not phost:
+ addrinfo_list = socket.getaddrinfo(
+ hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+ return addrinfo_list, False, None
+ else:
+ pport = pport and pport or 80
+ # when running on windows 10, the getaddrinfo used above
+ # returns a socktype 0. This generates an error exception:
+ # _on_error: exception Socket type must be stream or datagram, not 0
+ # Force the socket type to SOCK_STREAM
+ addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+ return addrinfo_list, True, pauth
+ except socket.gaierror as e:
+ raise WebSocketAddressException(e)
+
+
+def _open_socket(addrinfo_list, sockopt, timeout):
+ err = None
+ for addrinfo in addrinfo_list:
+ family, socktype, proto = addrinfo[:3]
+ sock = socket.socket(family, socktype, proto)
+ sock.settimeout(timeout)
+ for opts in DEFAULT_SOCKET_OPTION:
+ sock.setsockopt(*opts)
+ for opts in sockopt:
+ sock.setsockopt(*opts)
+
+ address = addrinfo[4]
+ err = None
+ while not err:
+ try:
+ sock.connect(address)
+ except socket.error as error:
+ sock.close()
+ error.remote_ip = str(address[0])
+ try:
+ eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED, errno.ENETUNREACH)
+ except AttributeError:
+ eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH)
+ if error.errno in eConnRefused:
+ err = error
+ continue
+ else:
+ raise error
+ else:
+ break
+ else:
+ continue
+ break
+ else:
+ if err:
+ raise err
+
+ return sock
+
+
+def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
+ context = sslopt.get('context', None)
+ if not context:
+ context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS_CLIENT))
+ # Non default context need to manually enable SSLKEYLOGFILE support by setting the keylog_filename attribute.
+ # For more details see also:
+ # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#context-creation
+ # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#ssl.SSLContext.keylog_filename
+ context.keylog_filename = os.environ.get("SSLKEYLOGFILE", None)
+
+ if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
+ cafile = sslopt.get('ca_certs', None)
+ capath = sslopt.get('ca_cert_path', None)
+ if cafile or capath:
+ context.load_verify_locations(cafile=cafile, capath=capath)
+ elif hasattr(context, 'load_default_certs'):
+ context.load_default_certs(ssl.Purpose.SERVER_AUTH)
+ if sslopt.get('certfile', None):
+ context.load_cert_chain(
+ sslopt['certfile'],
+ sslopt.get('keyfile', None),
+ sslopt.get('password', None),
+ )
+
+ # Python 3.10 switch to PROTOCOL_TLS_CLIENT defaults to "cert_reqs = ssl.CERT_REQUIRED" and "check_hostname = True"
+ # If both disabled, set check_hostname before verify_mode
+ # see https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153
+ if sslopt.get('cert_reqs', ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get('check_hostname', False):
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+ else:
+ context.check_hostname = sslopt.get('check_hostname', True)
+ context.verify_mode = sslopt.get('cert_reqs', ssl.CERT_REQUIRED)
+
+ if 'ciphers' in sslopt:
+ context.set_ciphers(sslopt['ciphers'])
+ if 'cert_chain' in sslopt:
+ certfile, keyfile, password = sslopt['cert_chain']
+ context.load_cert_chain(certfile, keyfile, password)
+ if 'ecdh_curve' in sslopt:
+ context.set_ecdh_curve(sslopt['ecdh_curve'])
+
+ return context.wrap_socket(
+ sock,
+ do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True),
+ suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True),
+ server_hostname=hostname,
+ )
+
+
+def _ssl_socket(sock, user_sslopt, hostname):
+ sslopt = dict(cert_reqs=ssl.CERT_REQUIRED)
+ sslopt.update(user_sslopt)
+
+ certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE')
+ if certPath and os.path.isfile(certPath) \
+ and user_sslopt.get('ca_certs', None) is None:
+ sslopt['ca_certs'] = certPath
+ elif certPath and os.path.isdir(certPath) \
+ and user_sslopt.get('ca_cert_path', None) is None:
+ sslopt['ca_cert_path'] = certPath
+
+ if sslopt.get('server_hostname', None):
+ hostname = sslopt['server_hostname']
+
+ check_hostname = sslopt.get('check_hostname', True)
+ sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
+
+ return sock
+
+
+def _tunnel(sock, host, port, auth):
+ debug("Connecting proxy...")
+ connect_header = "CONNECT {h}:{p} HTTP/1.1\r\n".format(h=host, p=port)
+ connect_header += "Host: {h}:{p}\r\n".format(h=host, p=port)
+
+ # TODO: support digest auth.
+ if auth and auth[0]:
+ auth_str = auth[0]
+ if auth[1]:
+ auth_str += ":" + auth[1]
+ encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '')
+ connect_header += "Proxy-Authorization: Basic {str}\r\n".format(str=encoded_str)
+ connect_header += "\r\n"
+ dump("request header", connect_header)
+
+ send(sock, connect_header)
+
+ try:
+ status, resp_headers, status_message = read_headers(sock)
+ except Exception as e:
+ raise WebSocketProxyException(str(e))
+
+ if status != 200:
+ raise WebSocketProxyException(
+ "failed CONNECT via proxy status: {status}".format(status=status))
+
+ return sock
+
+
+def read_headers(sock):
+ status = None
+ status_message = None
+ headers = {}
+ trace("--- response header ---")
+
+ while True:
+ line = recv_line(sock)
+ line = line.decode('utf-8').strip()
+ if not line:
+ break
+ trace(line)
+ if not status:
+
+ status_info = line.split(" ", 2)
+ status = int(status_info[1])
+ if len(status_info) > 2:
+ status_message = status_info[2]
+ else:
+ kv = line.split(":", 1)
+ if len(kv) == 2:
+ key, value = kv
+ if key.lower() == "set-cookie" and headers.get("set-cookie"):
+ headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip()
+ else:
+ headers[key.lower()] = value.strip()
+ else:
+ raise WebSocketException("Invalid header")
+
+ trace("-----------------------")
+
+ return status, headers, status_message
diff --git a/contrib/python/websocket-client/py3/websocket/_logging.py b/contrib/python/websocket-client/py3/websocket/_logging.py
new file mode 100644
index 0000000000..806de4d41f
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_logging.py
@@ -0,0 +1,93 @@
+import logging
+
+"""
+_logging.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+_logger = logging.getLogger('websocket')
+try:
+ from logging import NullHandler
+except ImportError:
+ class NullHandler(logging.Handler):
+ def emit(self, record) -> None:
+ pass
+
+_logger.addHandler(NullHandler())
+
+_traceEnabled = False
+
+__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
+ "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
+
+
+def enableTrace(traceable: bool,
+ handler: logging.StreamHandler = logging.StreamHandler(),
+ level: str = "DEBUG") -> None:
+ """
+ Turn on/off the traceability.
+
+ Parameters
+ ----------
+ traceable: bool
+ If set to True, traceability is enabled.
+ """
+ global _traceEnabled
+ _traceEnabled = traceable
+ if traceable:
+ _logger.addHandler(handler)
+ _logger.setLevel(getattr(logging, level))
+
+
+def dump(title: str, message: str) -> None:
+ if _traceEnabled:
+ _logger.debug("--- " + title + " ---")
+ _logger.debug(message)
+ _logger.debug("-----------------------")
+
+
+def error(msg: str) -> None:
+ _logger.error(msg)
+
+
+def warning(msg: str) -> None:
+ _logger.warning(msg)
+
+
+def debug(msg: str) -> None:
+ _logger.debug(msg)
+
+
+def info(msg: str) -> None:
+ _logger.info(msg)
+
+
+def trace(msg: str) -> None:
+ if _traceEnabled:
+ _logger.debug(msg)
+
+
+def isEnabledForError() -> bool:
+ return _logger.isEnabledFor(logging.ERROR)
+
+
+def isEnabledForDebug() -> bool:
+ return _logger.isEnabledFor(logging.DEBUG)
+
+
+def isEnabledForTrace() -> bool:
+ return _traceEnabled
diff --git a/contrib/python/websocket-client/py3/websocket/_socket.py b/contrib/python/websocket-client/py3/websocket/_socket.py
new file mode 100644
index 0000000000..1575a0c0c3
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_socket.py
@@ -0,0 +1,181 @@
+import errno
+import selectors
+import socket
+
+from typing import Union
+
+from ._exceptions import *
+from ._ssl_compat import *
+from ._utils import *
+
+"""
+_socket.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)]
+if hasattr(socket, "SO_KEEPALIVE"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
+if hasattr(socket, "TCP_KEEPIDLE"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30))
+if hasattr(socket, "TCP_KEEPINTVL"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10))
+if hasattr(socket, "TCP_KEEPCNT"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3))
+
+_default_timeout = None
+
+__all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout",
+ "recv", "recv_line", "send"]
+
+
+class sock_opt:
+
+ def __init__(self, sockopt: list, sslopt: dict) -> None:
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ self.sockopt = sockopt
+ self.sslopt = sslopt
+ self.timeout = None
+
+
+def setdefaulttimeout(timeout: Union[int, float, None]) -> None:
+ """
+ Set the global timeout setting to connect.
+
+ Parameters
+ ----------
+ timeout: int or float
+ default socket timeout time (in seconds)
+ """
+ global _default_timeout
+ _default_timeout = timeout
+
+
+def getdefaulttimeout() -> Union[int, float, None]:
+ """
+ Get default timeout
+
+ Returns
+ ----------
+ _default_timeout: int or float
+ Return the global timeout setting (in seconds) to connect.
+ """
+ return _default_timeout
+
+
+def recv(sock: socket.socket, bufsize: int) -> bytes:
+ if not sock:
+ raise WebSocketConnectionClosedException("socket is already closed.")
+
+ def _recv():
+ try:
+ return sock.recv(bufsize)
+ except SSLWantReadError:
+ pass
+ except socket.error as exc:
+ error_code = extract_error_code(exc)
+ if error_code != errno.EAGAIN and error_code != errno.EWOULDBLOCK:
+ raise
+
+ sel = selectors.DefaultSelector()
+ sel.register(sock, selectors.EVENT_READ)
+
+ r = sel.select(sock.gettimeout())
+ sel.close()
+
+ if r:
+ return sock.recv(bufsize)
+
+ try:
+ if sock.gettimeout() == 0:
+ 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)
+ except SSLError as e:
+ message = extract_err_message(e)
+ if isinstance(message, str) and 'timed out' in message:
+ raise WebSocketTimeoutException(message)
+ else:
+ raise
+
+ if not bytes_:
+ raise WebSocketConnectionClosedException(
+ "Connection to remote host was lost.")
+
+ return bytes_
+
+
+def recv_line(sock: socket.socket) -> bytes:
+ line = []
+ while True:
+ c = recv(sock, 1)
+ line.append(c)
+ if c == b'\n':
+ break
+ return b''.join(line)
+
+
+def send(sock: socket.socket, data: Union[bytes, str]) -> int:
+ if isinstance(data, str):
+ data = data.encode('utf-8')
+
+ if not sock:
+ raise WebSocketConnectionClosedException("socket is already closed.")
+
+ def _send():
+ try:
+ return sock.send(data)
+ except SSLWantWriteError:
+ pass
+ except socket.error as exc:
+ error_code = extract_error_code(exc)
+ if error_code is None:
+ raise
+ if error_code != errno.EAGAIN and error_code != errno.EWOULDBLOCK:
+ raise
+
+ sel = selectors.DefaultSelector()
+ sel.register(sock, selectors.EVENT_WRITE)
+
+ w = sel.select(sock.gettimeout())
+ sel.close()
+
+ if w:
+ return sock.send(data)
+
+ try:
+ if sock.gettimeout() == 0:
+ return sock.send(data)
+ else:
+ return _send()
+ except socket.timeout as e:
+ message = extract_err_message(e)
+ raise WebSocketTimeoutException(message)
+ except Exception as e:
+ message = extract_err_message(e)
+ if isinstance(message, str) and "timed out" in message:
+ raise WebSocketTimeoutException(message)
+ else:
+ raise
diff --git a/contrib/python/websocket-client/py3/websocket/_ssl_compat.py b/contrib/python/websocket-client/py3/websocket/_ssl_compat.py
new file mode 100644
index 0000000000..b2eba3877b
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_ssl_compat.py
@@ -0,0 +1,39 @@
+"""
+_ssl_compat.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"]
+
+try:
+ import ssl
+ from ssl import SSLError
+ from ssl import SSLWantReadError
+ from ssl import SSLWantWriteError
+ HAVE_SSL = True
+except ImportError:
+ # dummy class of SSLError for environment without ssl support
+ class SSLError(Exception):
+ pass
+
+ class SSLWantReadError(Exception):
+ pass
+
+ class SSLWantWriteError(Exception):
+ pass
+
+ ssl = None
+ HAVE_SSL = False
diff --git a/contrib/python/websocket-client/py3/websocket/_url.py b/contrib/python/websocket-client/py3/websocket/_url.py
new file mode 100644
index 0000000000..a330615485
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_url.py
@@ -0,0 +1,169 @@
+import os
+import socket
+import struct
+
+from typing import Optional
+from urllib.parse import unquote, urlparse
+
+"""
+_url.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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__ = ["parse_url", "get_proxy_info"]
+
+
+def parse_url(url: str) -> tuple:
+ """
+ parse url and the result is tuple of
+ (hostname, port, resource path and the flag of secure mode)
+
+ Parameters
+ ----------
+ url: str
+ url string.
+ """
+ if ":" not in url:
+ raise ValueError("url is invalid")
+
+ scheme, url = url.split(":", 1)
+
+ parsed = urlparse(url, scheme="http")
+ if parsed.hostname:
+ hostname = parsed.hostname
+ else:
+ raise ValueError("hostname is invalid")
+ port = 0
+ if parsed.port:
+ port = parsed.port
+
+ is_secure = False
+ if scheme == "ws":
+ if not port:
+ port = 80
+ elif scheme == "wss":
+ is_secure = True
+ if not port:
+ port = 443
+ else:
+ raise ValueError("scheme %s is invalid" % scheme)
+
+ if parsed.path:
+ resource = parsed.path
+ else:
+ resource = "/"
+
+ if parsed.query:
+ resource += "?" + parsed.query
+
+ return hostname, port, resource, is_secure
+
+
+DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"]
+
+
+def _is_ip_address(addr: str) -> bool:
+ try:
+ socket.inet_aton(addr)
+ except socket.error:
+ return False
+ else:
+ return True
+
+
+def _is_subnet_address(hostname: str) -> bool:
+ try:
+ addr, netmask = hostname.split("/")
+ return _is_ip_address(addr) and 0 <= int(netmask) < 32
+ except ValueError:
+ return False
+
+
+def _is_address_in_network(ip: str, net: str) -> bool:
+ ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0]
+ netaddr, netmask = net.split('/')
+ netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0]
+
+ netmask = (0xFFFFFFFF << (32 - int(netmask))) & 0xFFFFFFFF
+ return ipaddr & netmask == netaddr
+
+
+def _is_no_proxy_host(hostname: str, no_proxy: Optional[list]) -> bool:
+ if not no_proxy:
+ v = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(" ", "")
+ if v:
+ no_proxy = v.split(",")
+ if not no_proxy:
+ no_proxy = DEFAULT_NO_PROXY_HOST
+
+ if '*' in no_proxy:
+ return True
+ if hostname in no_proxy:
+ return True
+ if _is_ip_address(hostname):
+ return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)])
+ for domain in [domain for domain in no_proxy if domain.startswith('.')]:
+ if hostname.endswith(domain):
+ return True
+ return False
+
+
+def get_proxy_info(
+ hostname: str, is_secure: bool, proxy_host: Optional[str] = None, proxy_port: int = 0, proxy_auth: Optional[tuple] = None,
+ no_proxy: Optional[list] = None, proxy_type: str = 'http') -> tuple:
+ """
+ Try to retrieve proxy host and port from environment
+ if not provided in options.
+ Result is (proxy_host, proxy_port, proxy_auth).
+ proxy_auth is tuple of username and password
+ of proxy authentication information.
+
+ Parameters
+ ----------
+ hostname: str
+ Websocket server name.
+ is_secure: bool
+ Is the connection secure? (wss) looks for "https_proxy" in env
+ instead of "http_proxy"
+ proxy_host: str
+ http proxy host name.
+ proxy_port: str or int
+ http proxy port.
+ no_proxy: list
+ Whitelisted host names that don't use the proxy.
+ proxy_auth: tuple
+ HTTP proxy auth information. Tuple of username and password. Default is None.
+ proxy_type: str
+ Specify the proxy protocol (http, socks4, socks4a, socks5, socks5h). Default is "http".
+ Use socks4a or socks5h if you want to send DNS requests through the proxy.
+ """
+ if _is_no_proxy_host(hostname, no_proxy):
+ return None, 0, None
+
+ if proxy_host:
+ port = proxy_port
+ auth = proxy_auth
+ return proxy_host, port, auth
+
+ env_key = "https_proxy" if is_secure else "http_proxy"
+ value = os.environ.get(env_key, os.environ.get(env_key.upper(), "")).replace(" ", "")
+ if value:
+ proxy = urlparse(value)
+ auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None
+ return proxy.hostname, proxy.port, auth
+
+ return None, 0, None
diff --git a/contrib/python/websocket-client/py3/websocket/_utils.py b/contrib/python/websocket-client/py3/websocket/_utils.py
new file mode 100644
index 0000000000..62ba0b01b8
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_utils.py
@@ -0,0 +1,106 @@
+from typing import Union
+
+"""
+_url.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"]
+
+
+class NoLock:
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
+ pass
+
+
+try:
+ # If wsaccel is available we use compiled routines to validate UTF-8
+ # strings.
+ from wsaccel.utf8validator import Utf8Validator
+
+ def _validate_utf8(utfbytes: bytes) -> bool:
+ return Utf8Validator().validate(utfbytes)[0]
+
+except ImportError:
+ # UTF-8 validator
+ # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+
+ _UTF8_ACCEPT = 0
+ _UTF8_REJECT = 12
+
+ _UTF8D = [
+ # The first part of the table maps bytes to character classes that
+ # to reduce the size of the transition table and create bitmasks.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+ # The second part is a transition table that maps a combination
+ # of a state of the automaton and a character class to a state.
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12, ]
+
+ def _decode(state: int, codep: int, ch: int) -> tuple:
+ tp = _UTF8D[ch]
+
+ codep = (ch & 0x3f) | (codep << 6) if (
+ state != _UTF8_ACCEPT) else (0xff >> tp) & ch
+ state = _UTF8D[256 + state + tp]
+
+ return state, codep
+
+ def _validate_utf8(utfbytes: Union[str, bytes]) -> bool:
+ state = _UTF8_ACCEPT
+ codep = 0
+ for i in utfbytes:
+ state, codep = _decode(state, codep, i)
+ if state == _UTF8_REJECT:
+ return False
+
+ return True
+
+
+def validate_utf8(utfbytes: Union[str, bytes]) -> bool:
+ """
+ validate utf8 byte string.
+ utfbytes: utf byte string to check.
+ return value: if valid utf8 string, return true. Otherwise, return false.
+ """
+ return _validate_utf8(utfbytes)
+
+
+def extract_err_message(exception: Exception) -> Union[str, None]:
+ if exception.args:
+ return exception.args[0]
+ else:
+ return None
+
+
+def extract_error_code(exception: Exception) -> Union[int, None]:
+ if exception.args and len(exception.args) > 1:
+ return exception.args[0] if isinstance(exception.args[0], int) else None
diff --git a/contrib/python/websocket-client/py3/websocket/_wsdump.py b/contrib/python/websocket-client/py3/websocket/_wsdump.py
new file mode 100644
index 0000000000..d637ce2b45
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/_wsdump.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+
+"""
+wsdump.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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 argparse
+import code
+import sys
+import threading
+import time
+import ssl
+import gzip
+import zlib
+from urllib.parse import urlparse
+
+import websocket
+
+try:
+ import readline
+except ImportError:
+ pass
+
+
+def get_encoding() -> str:
+ encoding = getattr(sys.stdin, "encoding", "")
+ if not encoding:
+ return "utf-8"
+ else:
+ return encoding.lower()
+
+
+OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
+ENCODING = get_encoding()
+
+
+class VAction(argparse.Action):
+
+ def __call__(self, parser: argparse.Namespace, args: tuple, values: str, option_string: str = None) -> None:
+ if values is None:
+ values = "1"
+ try:
+ values = int(values)
+ except ValueError:
+ values = values.count("v") + 1
+ setattr(args, self.dest, values)
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool")
+ parser.add_argument("url", metavar="ws_url",
+ 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,
+ dest="verbose",
+ help="set verbose mode. If set to 1, show opcode. "
+ "If set to 2, enable to trace websocket module")
+ parser.add_argument("-n", "--nocert", action='store_true',
+ help="Ignore invalid SSL cert")
+ parser.add_argument("-r", "--raw", action="store_true",
+ help="raw output")
+ parser.add_argument("-s", "--subprotocols", nargs='*',
+ help="Set subprotocols")
+ parser.add_argument("-o", "--origin",
+ help="Set origin")
+ parser.add_argument("--eof-wait", default=0, type=int,
+ help="wait time(second) after 'EOF' received.")
+ parser.add_argument("-t", "--text",
+ help="Send initial text")
+ parser.add_argument("--timings", action="store_true",
+ help="Print timings in seconds")
+ parser.add_argument("--headers",
+ help="Set custom headers. Use ',' as separator")
+
+ return parser.parse_args()
+
+
+class RawInput:
+
+ def raw_input(self, prompt: str = "") -> str:
+ line = input(prompt)
+
+ if ENCODING and ENCODING != "utf-8" and not isinstance(line, str):
+ line = line.decode(ENCODING).encode("utf-8")
+ elif isinstance(line, str):
+ line = line.encode("utf-8")
+
+ return line
+
+
+class InteractiveConsole(RawInput, code.InteractiveConsole):
+
+ def write(self, data: str) -> None:
+ sys.stdout.write("\033[2K\033[E")
+ # sys.stdout.write("\n")
+ sys.stdout.write("\033[34m< " + data + "\033[39m")
+ sys.stdout.write("\n> ")
+ sys.stdout.flush()
+
+ def read(self) -> str:
+ return self.raw_input("> ")
+
+
+class NonInteractive(RawInput):
+
+ def write(self, data: str) -> None:
+ sys.stdout.write(data)
+ sys.stdout.write("\n")
+ sys.stdout.flush()
+
+ def read(self) -> str:
+ return self.raw_input("")
+
+
+def main() -> None:
+ start_time = time.time()
+ args = parse_args()
+ if args.verbose > 1:
+ websocket.enableTrace(True)
+ options = {}
+ if args.proxy:
+ p = urlparse(args.proxy)
+ options["http_proxy_host"] = p.hostname
+ options["http_proxy_port"] = p.port
+ if args.origin:
+ options["origin"] = args.origin
+ if args.subprotocols:
+ options["subprotocols"] = args.subprotocols
+ opts = {}
+ if args.nocert:
+ opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False}
+ if args.headers:
+ options['header'] = list(map(str.strip, args.headers.split(',')))
+ ws = websocket.create_connection(args.url, sslopt=opts, **options)
+ if args.raw:
+ console = NonInteractive()
+ else:
+ console = InteractiveConsole()
+ print("Press Ctrl+C to quit")
+
+ def recv() -> tuple:
+ try:
+ frame = ws.recv_frame()
+ except websocket.WebSocketException:
+ return websocket.ABNF.OPCODE_CLOSE, ""
+ if not frame:
+ raise websocket.WebSocketException("Not a valid frame {frame}".format(frame=frame))
+ elif frame.opcode in OPCODE_DATA:
+ return frame.opcode, frame.data
+ elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
+ ws.send_close()
+ return frame.opcode, ""
+ elif frame.opcode == websocket.ABNF.OPCODE_PING:
+ ws.pong(frame.data)
+ return frame.opcode, frame.data
+
+ return frame.opcode, frame.data
+
+ def recv_ws() -> None:
+ while True:
+ opcode, data = recv()
+ msg = None
+ if opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes):
+ data = str(data, "utf-8")
+ if isinstance(data, bytes) and len(data) > 2 and data[:2] == b'\037\213': # gzip magick
+ try:
+ data = "[gzip] " + str(gzip.decompress(data), "utf-8")
+ except:
+ pass
+ elif isinstance(data, bytes):
+ try:
+ data = "[zlib] " + str(zlib.decompress(data, -zlib.MAX_WBITS), "utf-8")
+ except:
+ pass
+
+ if isinstance(data, bytes):
+ data = repr(data)
+
+ if args.verbose:
+ msg = "{opcode}: {data}".format(opcode=websocket.ABNF.OPCODE_MAP.get(opcode), data=data)
+ else:
+ msg = data
+
+ if msg is not None:
+ if args.timings:
+ console.write(str(time.time() - start_time) + ": " + msg)
+ else:
+ console.write(msg)
+
+ if opcode == websocket.ABNF.OPCODE_CLOSE:
+ break
+
+ thread = threading.Thread(target=recv_ws)
+ thread.daemon = True
+ thread.start()
+
+ if args.text:
+ ws.send(args.text)
+
+ while True:
+ try:
+ message = console.read()
+ ws.send(message)
+ except KeyboardInterrupt:
+ return
+ except EOFError:
+ time.sleep(args.eof_wait)
+ return
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except Exception as e:
+ print(e)
diff --git a/contrib/python/websocket-client/py3/websocket/tests/__init__.py b/contrib/python/websocket-client/py3/websocket/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/__init__.py
diff --git a/contrib/python/websocket-client/py3/websocket/tests/data/header01.txt b/contrib/python/websocket-client/py3/websocket/tests/data/header01.txt
new file mode 100644
index 0000000000..d44d24c205
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/data/header01.txt
@@ -0,0 +1,6 @@
+HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade: WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/contrib/python/websocket-client/py3/websocket/tests/data/header02.txt b/contrib/python/websocket-client/py3/websocket/tests/data/header02.txt
new file mode 100644
index 0000000000..f481de928a
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/data/header02.txt
@@ -0,0 +1,6 @@
+HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/contrib/python/websocket-client/py3/websocket/tests/data/header03.txt b/contrib/python/websocket-client/py3/websocket/tests/data/header03.txt
new file mode 100644
index 0000000000..1a81dc70ce
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/data/header03.txt
@@ -0,0 +1,8 @@
+HTTP/1.1 101 WebSocket Protocol Handshake
+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/contrib/python/websocket-client/py3/websocket/tests/test_abnf.py b/contrib/python/websocket-client/py3/websocket/tests/test_abnf.py
new file mode 100644
index 0000000000..dbf9b636a3
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_abnf.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+#
+import websocket as ws
+from websocket._abnf import *
+import unittest
+
+"""
+test_abnf.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+
+class ABNFTest(unittest.TestCase):
+
+ def testInit(self):
+ a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
+ self.assertEqual(a.fin, 0)
+ self.assertEqual(a.rsv1, 0)
+ self.assertEqual(a.rsv2, 0)
+ self.assertEqual(a.rsv3, 0)
+ self.assertEqual(a.opcode, 9)
+ self.assertEqual(a.data, '')
+ a_bad = ABNF(0,1,0,0, opcode=77)
+ self.assertEqual(a_bad.rsv1, 1)
+ self.assertEqual(a_bad.opcode, 77)
+
+ def testValidate(self):
+ a_invalid_ping = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING)
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_invalid_ping.validate, skip_utf8_validation=False)
+ a_bad_rsv_value = ABNF(0,1,0,0, opcode=ABNF.OPCODE_TEXT)
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_rsv_value.validate, skip_utf8_validation=False)
+ a_bad_opcode = ABNF(0,0,0,0, opcode=77)
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_opcode.validate, skip_utf8_validation=False)
+ a_bad_close_frame = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01')
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame.validate, skip_utf8_validation=False)
+ a_bad_close_frame_2 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01\x8a\xaa\xff\xdd')
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_2.validate, skip_utf8_validation=False)
+ a_bad_close_frame_3 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x03\xe7')
+ self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_3.validate, skip_utf8_validation=True)
+
+ def testMask(self):
+ abnf_none_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data=None)
+ bytes_val = b"aaaa"
+ self.assertEqual(abnf_none_data._get_masked(bytes_val), bytes_val)
+ abnf_str_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data="a")
+ self.assertEqual(abnf_str_data._get_masked(bytes_val), b'aaaa\x00')
+
+ def testFormat(self):
+ abnf_bad_rsv_bits = ABNF(2,0,0,0, opcode=ABNF.OPCODE_TEXT)
+ self.assertRaises(ValueError, abnf_bad_rsv_bits.format)
+ abnf_bad_opcode = ABNF(0,0,0,0, opcode=5)
+ self.assertRaises(ValueError, abnf_bad_opcode.format)
+ abnf_length_10 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, data="abcdefghij")
+ self.assertEqual(b'\x01', abnf_length_10.format()[0].to_bytes(1, 'big'))
+ self.assertEqual(b'\x8a', abnf_length_10.format()[1].to_bytes(1, 'big'))
+ self.assertEqual("fin=0 opcode=1 data=abcdefghij", abnf_length_10.__str__())
+ abnf_length_20 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_BINARY, data="abcdefghijabcdefghij")
+ self.assertEqual(b'\x02', abnf_length_20.format()[0].to_bytes(1, 'big'))
+ self.assertEqual(b'\x94', abnf_length_20.format()[1].to_bytes(1, 'big'))
+ abnf_no_mask = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, mask=0, data=b'\x01\x8a\xcc')
+ self.assertEqual(b'\x01\x03\x01\x8a\xcc', abnf_no_mask.format())
+
+ def testFrameBuffer(self):
+ fb = frame_buffer(0, True)
+ self.assertEqual(fb.recv, 0)
+ self.assertEqual(fb.skip_utf8_validation, True)
+ fb.clear
+ self.assertEqual(fb.header, None)
+ self.assertEqual(fb.length, None)
+ self.assertEqual(fb.mask, None)
+ self.assertEqual(fb.has_mask(), False)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/websocket/tests/test_app.py b/contrib/python/websocket-client/py3/websocket/tests/test_app.py
new file mode 100644
index 0000000000..ff90a0aa87
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_app.py
@@ -0,0 +1,299 @@
+# -*- 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 2023 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.
+"""
+
+# 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
+LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1')
+TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1'
+TRACEABLE = True
+
+
+class WebSocketAppTest(unittest.TestCase):
+
+ class NotSetYet:
+ """ A marker class for signalling that a value hasn't been set yet.
+ """
+
+ def setUp(self):
+ ws.enableTrace(TRACEABLE)
+
+ 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):
+ """ 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):
+ """ Set the keep_running flag for later inspection and immediately
+ close the connection.
+ """
+ self.send("hello!")
+ WebSocketAppTest.keep_running_open = self.keep_running
+ self.keep_running = False
+
+ def on_message(wsapp, message):
+ print(message)
+ self.close()
+
+ def on_close(self, *args, **kwargs):
+ """ Set the keep_running flag for the test to use.
+ """
+ WebSocketAppTest.keep_running_close = self.keep_running
+
+ 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_INTERNET, "Internet-requiring tests are disabled")
+ def testSockMaskKey(self):
+ """ A WebSocketApp should forward the received mask_key function down
+ to the actual socket.
+ """
+
+ def my_mask_key_func():
+ return "\x00\x00\x00\x00"
+
+ 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'.
+ self.assertEqual(id(app.get_mask_key), id(my_mask_key_func))
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testInvalidPingIntervalPingTimeout(self):
+ """ Test exception handling if ping_interval < ping_timeout
+ """
+
+ def on_ping(app, msg):
+ print("Got a ping!")
+ app.close()
+
+ def on_pong(app, msg):
+ print("Got a pong! No need to respond")
+ app.close()
+
+ app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong)
+ self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=1, ping_timeout=2, sslopt={"cert_reqs": ssl.CERT_NONE})
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testPingInterval(self):
+ """ Test WebSocketApp proper ping functionality
+ """
+
+ def on_ping(app, msg):
+ print("Got a ping!")
+ app.close()
+
+ def on_pong(app, msg):
+ print("Got a pong! No need to respond")
+ app.close()
+
+ app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong)
+ app.run_forever(ping_interval=2, ping_timeout=1, sslopt={"cert_reqs": ssl.CERT_NONE})
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testOpcodeClose(self):
+ """ Test WebSocketApp close opcode
+ """
+
+ app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect')
+ app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload")
+
+ # This is commented out because the URL no longer responds in the expected way
+ # @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ # def testOpcodeBinary(self):
+ # """ Test WebSocketApp binary opcode
+ # """
+ # 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")
+ def testBadPingInterval(self):
+ """ A WebSocketApp handling of negative ping_interval
+ """
+ app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1')
+ self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=-5, sslopt={"cert_reqs": ssl.CERT_NONE})
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testBadPingTimeout(self):
+ """ A WebSocketApp handling of negative ping_timeout
+ """
+ app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1')
+ self.assertRaises(ws.WebSocketException, app.run_forever, ping_timeout=-3, sslopt={"cert_reqs": ssl.CERT_NONE})
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testCloseStatusCode(self):
+ """ Test extraction of close frame status code and close reason in WebSocketApp
+ """
+ def on_close(wsapp, close_status_code, close_msg):
+ print("on_close reached")
+
+ app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect', on_close=on_close)
+ closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'\x03\xe8no-init-from-client')
+ self.assertEqual([1000, 'no-init-from-client'], app._get_close_args(closeframe))
+
+ closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'')
+ self.assertEqual([None, None], app._get_close_args(closeframe))
+
+ app2 = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect')
+ closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'')
+ self.assertEqual([None, None], app2._get_close_args(closeframe))
+
+ self.assertRaises(ws.WebSocketConnectionClosedException, app.send, data="test if connection is closed")
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testCallbackFunctionException(self):
+ """ Test callback function exception handling """
+
+ exc = None
+ passed_app = None
+
+ def on_open(app):
+ raise RuntimeError("Callback failed")
+
+ def on_error(app, err):
+ nonlocal passed_app
+ passed_app = app
+ nonlocal exc
+ exc = err
+
+ def on_pong(app, msg):
+ app.close()
+
+ app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_error=on_error, on_pong=on_pong)
+ app.run_forever(ping_interval=2, ping_timeout=1)
+
+ self.assertEqual(passed_app, app)
+ self.assertIsInstance(exc, RuntimeError)
+ self.assertEqual(str(exc), "Callback failed")
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testCallbackMethodException(self):
+ """ Test callback method exception handling """
+
+ class Callbacks:
+ def __init__(self):
+ self.exc = None
+ self.passed_app = None
+ self.app = ws.WebSocketApp(
+ 'ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT,
+ on_open=self.on_open,
+ on_error=self.on_error,
+ on_pong=self.on_pong
+ )
+ self.app.run_forever(ping_interval=2, ping_timeout=1)
+
+ def on_open(self, app):
+ raise RuntimeError("Callback failed")
+
+ def on_error(self, app, err):
+ self.passed_app = app
+ self.exc = err
+
+ def on_pong(self, app, msg):
+ app.close()
+
+ callbacks = Callbacks()
+
+ self.assertEqual(callbacks.passed_app, callbacks.app)
+ self.assertIsInstance(callbacks.exc, RuntimeError)
+ self.assertEqual(str(callbacks.exc), "Callback failed")
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testReconnect(self):
+ """ Test reconnect """
+ pong_count = 0
+ exc = None
+
+ def on_error(app, err):
+ nonlocal exc
+ exc = err
+
+ def on_pong(app, msg):
+ nonlocal pong_count
+ pong_count += 1
+ if pong_count == 1:
+ # First pong, shutdown socket, enforce read error
+ app.sock.shutdown()
+ if pong_count >= 2:
+ # Got second pong after reconnect
+ app.close()
+
+ app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_pong=on_pong, on_error=on_error)
+ app.run_forever(ping_interval=2, ping_timeout=1, reconnect=3)
+
+ self.assertEqual(pong_count, 2)
+ self.assertIsInstance(exc, ws.WebSocketTimeoutException)
+ self.assertEqual(str(exc), "ping/pong timed out")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/websocket/tests/test_cookiejar.py b/contrib/python/websocket-client/py3/websocket/tests/test_cookiejar.py
new file mode 100644
index 0000000000..8f835e9e7c
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_cookiejar.py
@@ -0,0 +1,116 @@
+import unittest
+from websocket._cookiejar import SimpleCookieJar
+
+"""
+test_cookiejar.py
+websocket - WebSocket client library for Python
+
+Copyright 2023 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.
+"""
+
+
+class CookieJarTest(unittest.TestCase):
+ def testAdd(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; domain=.abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; domain=abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+ self.assertTrue("abc" not in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get(None), "")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=.abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=xyz")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("xyz"), "e=f")
+ self.assertEqual(cookie_jar.get("something"), "")
+
+ def testSet(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; domain=.abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; domain=abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+ self.assertTrue("abc" not in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=.abc")
+ self.assertEqual(cookie_jar.get("abc"), "e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=xyz")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("xyz"), "e=f")
+ self.assertEqual(cookie_jar.get("something"), "")
+
+ def testGet(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc.com")
+ self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("abc.com.es"), "")
+ self.assertEqual(cookie_jar.get("xabc.com"), "")
+
+ cookie_jar.set("a=b; c=d; domain=.abc.com")
+ self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("abc.com.es"), "")
+ self.assertEqual(cookie_jar.get("xabc.com"), "")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/websocket/tests/test_http.py b/contrib/python/websocket-client/py3/websocket/tests/test_http.py
new file mode 100644
index 0000000000..456279f288
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_http.py
@@ -0,0 +1,177 @@
+# -*- 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 2023 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.
+"""
+
+try:
+ from python_socks._errors import ProxyError, ProxyTimeoutError, ProxyConnectionError
+except:
+ from websocket._http import ProxyError, ProxyTimeoutError, ProxyConnectionError
+
+# Skip test to access the internet unless TEST_WITH_INTERNET == 1
+TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1'
+TEST_WITH_PROXY = os.environ.get('TEST_WITH_PROXY', '0') == '1'
+# Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1
+LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1')
+TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1'
+
+
+class SockMock:
+ def __init__(self):
+ self.data = []
+ self.sent = []
+
+ def add_packet(self, data):
+ self.data.append(data)
+
+ def gettimeout(self):
+ return None
+
+ def recv(self, bufsize):
+ if self.data:
+ e = self.data.pop(0)
+ if isinstance(e, Exception):
+ raise e
+ if len(e) > bufsize:
+ self.data.insert(0, e[bufsize:])
+ return e[:bufsize]
+
+ def send(self, data):
+ self.sent.append(data)
+ return len(data)
+
+ def close(self):
+ pass
+
+
+class HeaderSockMock(SockMock):
+
+ def __init__(self, fname):
+ SockMock.__init__(self)
+ import yatest.common
+ path = yatest.common.source_path(os.path.join('contrib/python/websocket-client/py3/websocket/tests', fname))
+ with open(path, "rb") as f:
+ self.add_packet(f.read())
+
+
+class OptsList():
+
+ def __init__(self):
+ self.timeout = 1
+ self.sockopt = []
+ self.sslopt = {"cert_reqs": ssl.CERT_NONE}
+
+
+class HttpTest(unittest.TestCase):
+
+ def testReadHeader(self):
+ status, header, status_message = read_headers(HeaderSockMock("data/header01.txt"))
+ self.assertEqual(status, 101)
+ self.assertEqual(header["connection"], "Upgrade")
+ # header02.txt is intentionally malformed
+ self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt"))
+
+ def testTunnel(self):
+ self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header01.txt"), "example.com", 80, ("username", "password"))
+ self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header02.txt"), "example.com", 80, ("username", "password"))
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testConnect(self):
+ # Not currently testing an actual proxy connection, so just check whether proxy errors are raised. This requires internet for a DNS lookup
+ 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", 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", 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, "/")))
+ # The following test fails on Mac OS with a gaierror, not an OverflowError
+ # self.assertRaises(OverflowError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=99999, proxy_type="socks4", timeout=2), False)
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ @unittest.skipUnless(TEST_WITH_PROXY, "This test requires a HTTP proxy to be running on port 8899")
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testProxyConnect(self):
+ ws = websocket.WebSocket()
+ ws.connect("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")
+ ws.send("Hello, Server")
+ server_response = ws.recv()
+ self.assertEqual(server_response, "Hello, Server")
+ # self.assertEqual(_start_proxied_socket("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http"))[1], ("api.bitfinex.com", 443, '/ws/2'))
+ self.assertEqual(_get_addrinfo_list("api.bitfinex.com", 443, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")),
+ (socket.getaddrinfo("127.0.0.1", 8899, 0, socket.SOCK_STREAM, socket.SOL_TCP), True, None))
+ self.assertEqual(connect("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=8899, proxy_type="http"), None)[1], ("api.bitfinex.com", 443, '/ws/2'))
+ # TODO: Test SOCKS4 and SOCK5 proxies with unit tests
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testSSLopt(self):
+ ssloptions = {
+ "check_hostname": False,
+ "server_hostname": "ServerName",
+ "ssl_version": ssl.PROTOCOL_TLS_CLIENT,
+ "ciphers": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:\
+ TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
+ ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:\
+ ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
+ DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:\
+ ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:\
+ ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:\
+ DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:\
+ ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:\
+ ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA",
+ "ecdh_curve": "prime256v1"
+ }
+ ws_ssl1 = websocket.WebSocket(sslopt=ssloptions)
+ ws_ssl1.connect("wss://api.bitfinex.com/ws/2")
+ ws_ssl1.send("Hello")
+ ws_ssl1.close()
+
+ ws_ssl2 = websocket.WebSocket(sslopt={"check_hostname": True})
+ ws_ssl2.connect("wss://api.bitfinex.com/ws/2")
+ ws_ssl2.close
+
+ def testProxyInfo(self):
+ self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_protocol, "http")
+ self.assertRaises(ProxyError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval")
+ self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").proxy_host, "example.com")
+ self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_port, "8080")
+ self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None)
+ self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[0], "my_username123")
+ self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[1], "my_pass321")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/websocket/tests/test_url.py b/contrib/python/websocket-client/py3/websocket/tests/test_url.py
new file mode 100644
index 0000000000..a74dd7669d
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_url.py
@@ -0,0 +1,319 @@
+# -*- 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 2023 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.
+"""
+
+
+class UrlTest(unittest.TestCase):
+
+ def test_address_in_network(self):
+ self.assertTrue(_is_address_in_network('127.0.0.1', '127.0.0.0/8'))
+ self.assertTrue(_is_address_in_network('127.1.0.1', '127.0.0.0/8'))
+ self.assertFalse(_is_address_in_network('127.1.0.1', '127.0.0.0/24'))
+
+ def testParseUrl(self):
+ p = parse_url("ws://www.example.com/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com/r/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("wss://www.example.com:8080/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+ p = parse_url("wss://www.example.com:8080/r?key=value")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r?key=value")
+ self.assertEqual(p[3], True)
+
+ self.assertRaises(ValueError, parse_url, "http://www.example.com/r")
+
+ p = parse_url("ws://[2a03:4000:123:83::3]/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://[2a03:4000:123:83::3]:8080/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("wss://[2a03:4000:123:83::3]/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 443)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+ p = parse_url("wss://[2a03:4000:123:83::3]:8080/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+
+class IsNoProxyHostTest(unittest.TestCase):
+ def setUp(self):
+ self.no_proxy = os.environ.get("no_proxy", None)
+ if "no_proxy" in os.environ:
+ del os.environ["no_proxy"]
+
+ def tearDown(self):
+ if self.no_proxy:
+ os.environ["no_proxy"] = self.no_proxy
+ elif "no_proxy" in os.environ:
+ del os.environ["no_proxy"]
+
+ def testMatchAll(self):
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", ['*']))
+ self.assertTrue(_is_no_proxy_host("192.168.0.1", ['*']))
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", ['other.websocket.org', '*']))
+ os.environ['no_proxy'] = '*'
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
+ self.assertTrue(_is_no_proxy_host("192.168.0.1", None))
+ os.environ['no_proxy'] = 'other.websocket.org, *'
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
+
+ def testIpAddress(self):
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.1']))
+ self.assertFalse(_is_no_proxy_host("127.0.0.2", ['127.0.0.1']))
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", ['other.websocket.org', '127.0.0.1']))
+ self.assertFalse(_is_no_proxy_host("127.0.0.2", ['other.websocket.org', '127.0.0.1']))
+ os.environ['no_proxy'] = '127.0.0.1'
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
+ self.assertFalse(_is_no_proxy_host("127.0.0.2", None))
+ os.environ['no_proxy'] = 'other.websocket.org, 127.0.0.1'
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
+ self.assertFalse(_is_no_proxy_host("127.0.0.2", None))
+
+ def testIpAddressInRange(self):
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.0/8']))
+ self.assertTrue(_is_no_proxy_host("127.0.0.2", ['127.0.0.0/8']))
+ self.assertFalse(_is_no_proxy_host("127.1.0.1", ['127.0.0.0/24']))
+ os.environ['no_proxy'] = '127.0.0.0/8'
+ self.assertTrue(_is_no_proxy_host("127.0.0.1", None))
+ self.assertTrue(_is_no_proxy_host("127.0.0.2", None))
+ os.environ['no_proxy'] = '127.0.0.0/24'
+ self.assertFalse(_is_no_proxy_host("127.1.0.1", None))
+
+ def testHostnameMatch(self):
+ self.assertTrue(_is_no_proxy_host("my.websocket.org", ['my.websocket.org']))
+ self.assertTrue(_is_no_proxy_host("my.websocket.org", ['other.websocket.org', 'my.websocket.org']))
+ self.assertFalse(_is_no_proxy_host("my.websocket.org", ['other.websocket.org']))
+ os.environ['no_proxy'] = 'my.websocket.org'
+ self.assertTrue(_is_no_proxy_host("my.websocket.org", None))
+ self.assertFalse(_is_no_proxy_host("other.websocket.org", None))
+ os.environ['no_proxy'] = 'other.websocket.org, my.websocket.org'
+ self.assertTrue(_is_no_proxy_host("my.websocket.org", None))
+
+ def testHostnameMatchDomain(self):
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", ['.websocket.org']))
+ self.assertTrue(_is_no_proxy_host("my.other.websocket.org", ['.websocket.org']))
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", ['my.websocket.org', '.websocket.org']))
+ self.assertFalse(_is_no_proxy_host("any.websocket.com", ['.websocket.org']))
+ os.environ['no_proxy'] = '.websocket.org'
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
+ self.assertTrue(_is_no_proxy_host("my.other.websocket.org", None))
+ self.assertFalse(_is_no_proxy_host("any.websocket.com", None))
+ os.environ['no_proxy'] = 'my.websocket.org, .websocket.org'
+ self.assertTrue(_is_no_proxy_host("any.websocket.org", None))
+
+
+class ProxyInfoTest(unittest.TestCase):
+ def setUp(self):
+ self.http_proxy = os.environ.get("http_proxy", None)
+ self.https_proxy = os.environ.get("https_proxy", None)
+ self.no_proxy = os.environ.get("no_proxy", None)
+ if "http_proxy" in os.environ:
+ del os.environ["http_proxy"]
+ if "https_proxy" in os.environ:
+ del os.environ["https_proxy"]
+ if "no_proxy" in os.environ:
+ del os.environ["no_proxy"]
+
+ def tearDown(self):
+ if self.http_proxy:
+ os.environ["http_proxy"] = self.http_proxy
+ elif "http_proxy" in os.environ:
+ del os.environ["http_proxy"]
+
+ if self.https_proxy:
+ os.environ["https_proxy"] = self.https_proxy
+ elif "https_proxy" in os.environ:
+ del os.environ["https_proxy"]
+
+ if self.no_proxy:
+ os.environ["no_proxy"] = self.no_proxy
+ elif "no_proxy" in os.environ:
+ del os.environ["no_proxy"]
+
+ def testProxyFromArgs(self):
+ 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.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.events", False, proxy_host="localhost", proxy_auth=("a", "b")),
+ ("localhost", 0, ("a", "b")))
+ self.assertEqual(
+ 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.events", True, proxy_host="localhost", proxy_auth=("a", "b")),
+ ("localhost", 0, ("a", "b")))
+ self.assertEqual(
+ 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.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.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.events", False), ("localhost", None, None))
+ os.environ["http_proxy"] = "http://localhost:3128/"
+ 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.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.events", False), ("localhost", 3128, None))
+
+ os.environ["http_proxy"] = "http://localhost/"
+ os.environ["https_proxy"] = "http://localhost2/"
+ 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.events", True), ("localhost2", 3128, None))
+
+ os.environ["http_proxy"] = ""
+ os.environ["https_proxy"] = "http://localhost2/"
+ self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, None))
+ self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None))
+ os.environ["http_proxy"] = ""
+ os.environ["https_proxy"] = "http://localhost2:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None))
+ self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None))
+
+ os.environ["http_proxy"] = "http://localhost/"
+ os.environ["https_proxy"] = ""
+ self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None))
+ self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None))
+ os.environ["http_proxy"] = "http://localhost:3128/"
+ os.environ["https_proxy"] = ""
+ self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None))
+ self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None))
+
+ os.environ["http_proxy"] = "http://a:b@localhost/"
+ 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.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.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.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.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.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.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/"
+ os.environ["no_proxy"] = "example1.com,example2.com"
+ 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.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.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"] = "127.0.0.0/8, 192.168.0.0/16"
+ self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None))
+ self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/websocket/tests/test_websocket.py b/contrib/python/websocket-client/py3/websocket/tests/test_websocket.py
new file mode 100644
index 0000000000..54555c8b6c
--- /dev/null
+++ b/contrib/python/websocket-client/py3/websocket/tests/test_websocket.py
@@ -0,0 +1,456 @@
+# -*- 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 2023 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.
+"""
+
+try:
+ import ssl
+ from ssl import SSLError
+except ImportError:
+ # dummy class of SSLError for ssl none-support environment.
+ class SSLError(Exception):
+ pass
+
+# 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
+LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1')
+TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1'
+TRACEABLE = True
+
+
+def create_mask_key(_):
+ return "abcd"
+
+
+class SockMock:
+ def __init__(self):
+ self.data = []
+ self.sent = []
+
+ def add_packet(self, data):
+ self.data.append(data)
+
+ def gettimeout(self):
+ return None
+
+ def recv(self, bufsize):
+ if self.data:
+ e = self.data.pop(0)
+ if isinstance(e, Exception):
+ raise e
+ if len(e) > bufsize:
+ self.data.insert(0, e[bufsize:])
+ return e[:bufsize]
+
+ def send(self, data):
+ self.sent.append(data)
+ return len(data)
+
+ def close(self):
+ pass
+
+
+class HeaderSockMock(SockMock):
+
+ def __init__(self, fname):
+ SockMock.__init__(self)
+ import yatest.common
+ path = yatest.common.source_path(os.path.join('contrib/python/websocket-client/py3/websocket/tests', fname))
+ with open(path, "rb") as f:
+ self.add_packet(f.read())
+
+
+class WebSocketTest(unittest.TestCase):
+ def setUp(self):
+ ws.enableTrace(TRACEABLE)
+
+ def tearDown(self):
+ pass
+
+ def testDefaultTimeout(self):
+ self.assertEqual(ws.getdefaulttimeout(), None)
+ ws.setdefaulttimeout(10)
+ self.assertEqual(ws.getdefaulttimeout(), 10)
+ ws.setdefaulttimeout(None)
+
+ def testWSKey(self):
+ key = _create_sec_websocket_key()
+ self.assertTrue(key != 24)
+ self.assertTrue(str("¥n") not in key)
+
+ def testNonce(self):
+ """ WebSocket key should be a random 16-byte nonce.
+ """
+ key = _create_sec_websocket_key()
+ nonce = base64decode(key.encode("utf-8"))
+ self.assertEqual(16, len(nonce))
+
+ def testWsUtils(self):
+ key = "c6b8hTg4EeGb2gQMztV1/g=="
+ required_header = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+ "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0="}
+ self.assertEqual(_validate_header(required_header, key, None), (True, None))
+
+ header = required_header.copy()
+ header["upgrade"] = "http"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["upgrade"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["connection"] = "something"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["connection"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-accept"] = "something"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["sec-websocket-accept"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-protocol"] = "sub1"
+ self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1"))
+ # This case will print out a logging error using the error() function, but that is expected
+ self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-protocol"] = "sUb1"
+ self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1"))
+
+ header = required_header.copy()
+ # This case will print out a logging error using the error() function, but that is expected
+ self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (False, None))
+
+ def testReadHeader(self):
+ status, header, status_message = read_headers(HeaderSockMock("data/header01.txt"))
+ self.assertEqual(status, 101)
+ self.assertEqual(header["connection"], "Upgrade")
+
+ status, header, status_message = read_headers(HeaderSockMock("data/header03.txt"))
+ self.assertEqual(status, 101)
+ self.assertEqual(header["connection"], "Upgrade, Keep-Alive")
+
+ HeaderSockMock("data/header02.txt")
+ self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt"))
+
+ def testSend(self):
+ # TODO: add longer frame data
+ sock = ws.WebSocket()
+ sock.set_mask_key(create_mask_key)
+ s = sock.sock = HeaderSockMock("data/header01.txt")
+ sock.send("Hello")
+ self.assertEqual(s.sent[0], b'\x81\x85abcd)\x07\x0f\x08\x0e')
+
+ sock.send("こんにちは")
+ self.assertEqual(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc')
+
+# sock.send("x" * 5000)
+# self.assertEqual(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")
+
+ self.assertEqual(sock.send_binary(b'1111111111101'), 19)
+
+ def testRecv(self):
+ # TODO: add longer frame data
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ something = b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc'
+ s.add_packet(something)
+ data = sock.recv()
+ self.assertEqual(data, "こんにちは")
+
+ s.add_packet(b'\x81\x85abcd)\x07\x0f\x08\x0e')
+ data = sock.recv()
+ self.assertEqual(data, "Hello")
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testIter(self):
+ count = 2
+ 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://api.bitfinex.com/ws/2')
+ self.assertEqual(str, type(next(sock)))
+
+ def testInternalRecvStrict(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ s.add_packet(b'foo')
+ s.add_packet(socket.timeout())
+ s.add_packet(b'bar')
+ # s.add_packet(SSLError("The read operation timed out"))
+ s.add_packet(b'baz')
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.frame_buffer.recv_strict(9)
+ # with self.assertRaises(SSLError):
+ # data = sock._recv_strict(9)
+ data = sock.frame_buffer.recv_strict(9)
+ self.assertEqual(data, b'foobarbaz')
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.frame_buffer.recv_strict(1)
+
+ def testRecvTimeout(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ s.add_packet(b'\x81')
+ s.add_packet(socket.timeout())
+ s.add_packet(b'\x8dabcd\x29\x07\x0f\x08\x0e')
+ s.add_packet(socket.timeout())
+ s.add_packet(b'\x4e\x43\x33\x0e\x10\x0f\x00\x40')
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.recv()
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.recv()
+ data = sock.recv()
+ self.assertEqual(data, "Hello, World!")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithSimpleFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Brevity is "
+ s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
+ data = sock.recv()
+ self.assertEqual(data, "Brevity is the soul of wit")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithFireEventOfFragmentation(self):
+ sock = ws.WebSocket(fire_cont_frame=True)
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Brevity is "
+ s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
+ # OPCODE=CONT, FIN=0, MSG="Brevity is "
+ s.add_packet(b'\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
+
+ _, data = sock.recv_data()
+ self.assertEqual(data, b'Brevity is ')
+ _, data = sock.recv_data()
+ self.assertEqual(data, b'Brevity is ')
+ _, data = sock.recv_data()
+ self.assertEqual(data, b'the soul of wit')
+
+ # OPCODE=CONT, FIN=0, MSG="Brevity is "
+ s.add_packet(b'\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C')
+
+ with self.assertRaises(ws.WebSocketException):
+ sock.recv_data()
+
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testClose(self):
+ sock = ws.WebSocket()
+ sock.connected = True
+ sock.close
+
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ sock.connected = True
+ s.add_packet(b'\x88\x80\x17\x98p\x84')
+ sock.recv()
+ self.assertEqual(sock.connected, False)
+
+ def testRecvContFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17')
+ self.assertRaises(ws.WebSocketException, sock.recv)
+
+ def testRecvWithProlongedFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, "
+ s.add_packet(b'\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC')
+ # OPCODE=CONT, FIN=0, MSG="dear friends, "
+ s.add_packet(b'\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07\x17MB')
+ # OPCODE=CONT, FIN=1, MSG="once more"
+ s.add_packet(b'\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04')
+ data = sock.recv()
+ self.assertEqual(
+ data,
+ "Once more unto the breach, dear friends, once more")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithFragmentationAndControlFrame(self):
+ sock = ws.WebSocket()
+ sock.set_mask_key(create_mask_key)
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Too much "
+ s.add_packet(b'\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA')
+ # OPCODE=PING, FIN=1, MSG="Please PONG this"
+ s.add_packet(b'\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17')
+ # OPCODE=CONT, FIN=1, MSG="of a good thing"
+ s.add_packet(b'\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c\x08\x0c\x04')
+ data = sock.recv()
+ self.assertEqual(data, "Too much of a good thing")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+ self.assertEqual(
+ s.sent[0],
+ b'\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17')
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testWebSocket(self):
+ s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT)
+ self.assertNotEqual(s, None)
+ s.send("Hello, World")
+ result = s.next()
+ s.fileno()
+ self.assertEqual(result, "Hello, World")
+
+ s.send("こにゃにゃちは、世界")
+ result = s.recv()
+ self.assertEqual(result, "こにゃにゃちは、世界")
+ self.assertRaises(ValueError, s.send_close, -1, "")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testPingPong(self):
+ s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT)
+ self.assertNotEqual(s, None)
+ s.ping("Hello")
+ s.pong("Hi")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testSupportRedirect(self):
+ s = ws.WebSocket()
+ self.assertRaises(ws._exceptions.WebSocketBadStatusException, s.connect, "ws://google.com/")
+ # Need to find a URL that has a redirect code leading to a websocket
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testSecureWebSocket(self):
+ import ssl
+ s = ws.create_connection("wss://api.bitfinex.com/ws/2")
+ self.assertNotEqual(s, None)
+ self.assertTrue(isinstance(s.sock, ssl.SSLSocket))
+ self.assertEqual(s.getstatus(), 101)
+ self.assertNotEqual(s.getheaders(), None)
+ s.settimeout(10)
+ self.assertEqual(s.gettimeout(), 10)
+ self.assertEqual(s.getsubprotocol(), None)
+ s.abort()
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testWebSocketWithCustomHeader(self):
+ 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")
+ self.assertRaises(ValueError, s.close, -1, "")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testAfterClose(self):
+ s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT)
+ self.assertNotEqual(s, None)
+ s.close()
+ self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello")
+ self.assertRaises(ws.WebSocketConnectionClosedException, s.recv)
+
+
+class SockOptTest(unittest.TestCase):
+ @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled")
+ def testSockOpt(self):
+ sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),)
+ s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, sockopt=sockopt)
+ self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0)
+ s.close()
+
+
+class UtilsTest(unittest.TestCase):
+ def testUtf8Validator(self):
+ state = validate_utf8(b'\xf0\x90\x80\x80')
+ self.assertEqual(state, True)
+ state = validate_utf8(b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')
+ self.assertEqual(state, False)
+ state = validate_utf8(b'')
+ self.assertEqual(state, True)
+
+
+class HandshakeTest(unittest.TestCase):
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def test_http_SSL(self):
+ websock1 = ws.WebSocket(sslopt={"cert_chain": ssl.get_default_verify_paths().capath}, enable_multithread=False)
+ self.assertRaises(ValueError,
+ websock1.connect, "wss://api.bitfinex.com/ws/2")
+ websock2 = ws.WebSocket(sslopt={"certfile": "myNonexistentCertFile"})
+ self.assertRaises(FileNotFoundError,
+ websock2.connect, "wss://api.bitfinex.com/ws/2")
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testManualHeaders(self):
+ websock3 = ws.WebSocket(sslopt={"ca_certs": ssl.get_default_verify_paths().cafile,
+ "ca_cert_path": ssl.get_default_verify_paths().capath})
+ self.assertRaises(ws._exceptions.WebSocketBadStatusException,
+ websock3.connect, "wss://api.bitfinex.com/ws/2", cookie="chocolate",
+ origin="testing_websockets.com",
+ host="echo.websocket.events/websocket-client-test",
+ subprotocols=["testproto"],
+ connection="Upgrade",
+ header={"CustomHeader1":"123",
+ "Cookie":"TestValue",
+ "Sec-WebSocket-Key":"k9kFAUWNAMmf5OEMfTlOEA==",
+ "Sec-WebSocket-Protocol":"newprotocol"})
+
+ def testIPv6(self):
+ websock2 = ws.WebSocket()
+ self.assertRaises(ValueError, websock2.connect, "2001:4860:4860::8888")
+
+ def testBadURLs(self):
+ websock3 = ws.WebSocket()
+ self.assertRaises(ValueError, websock3.connect, "ws//example.com")
+ self.assertRaises(ws.WebSocketAddressException, websock3.connect, "ws://example")
+ self.assertRaises(ValueError, websock3.connect, "example.com")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/contrib/python/websocket-client/py3/ya.make b/contrib/python/websocket-client/py3/ya.make
new file mode 100644
index 0000000000..e2714d2d23
--- /dev/null
+++ b/contrib/python/websocket-client/py3/ya.make
@@ -0,0 +1,40 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(1.6.4)
+
+LICENSE(Apache-2.0)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ websocket/__init__.py
+ websocket/_abnf.py
+ websocket/_app.py
+ websocket/_cookiejar.py
+ websocket/_core.py
+ websocket/_exceptions.py
+ websocket/_handshake.py
+ websocket/_http.py
+ websocket/_logging.py
+ websocket/_socket.py
+ websocket/_ssl_compat.py
+ websocket/_url.py
+ websocket/_utils.py
+ websocket/_wsdump.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/websocket-client/py3/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)