Compare commits
28 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa519e095 | ||
|
|
d3a5839678 | ||
|
|
7e9f3c7343 | ||
|
|
4f04e6ca62 | ||
|
|
fd88b9e096 | ||
|
|
1cd63a031b | ||
|
|
db59b16e25 | ||
|
|
ba82ffe558 | ||
|
|
59dcd02a91 | ||
|
|
605194d515 | ||
|
|
689648ba32 | ||
|
|
29a3e0ebf6 | ||
|
|
aeaad0c330 | ||
|
|
12fa6c523b | ||
|
|
829e669403 | ||
|
|
fb3b66115e | ||
|
|
16b538d847 | ||
|
|
e622312a79 | ||
|
|
eb0324cb68 | ||
|
|
fd106e623d | ||
|
|
b3f61bb26d | ||
|
|
9e0bb069ad | ||
|
|
4b6c562825 | ||
|
|
a6f9260964 | ||
|
|
f5b157014d | ||
|
|
978a669d16 | ||
|
|
601ba59971 | ||
|
|
57da37e995 |
18 changed files with 487 additions and 174 deletions
|
|
@ -8,13 +8,12 @@ before_install:
|
||||||
- sudo apt-get install nodejs
|
- sudo apt-get install nodejs
|
||||||
install:
|
install:
|
||||||
- npm install -G socket.io
|
- npm install -G socket.io
|
||||||
- npm install -G http-proxy
|
- npm install -G yargs
|
||||||
- pip install -U requests
|
- pip install -U requests
|
||||||
- pip install -U six
|
- pip install -U six
|
||||||
- pip install -U websocket-client
|
- pip install -U websocket-client
|
||||||
- pip install -U coverage
|
- pip install -U coverage
|
||||||
before_script:
|
before_script:
|
||||||
- DEBUG=* node socketIO_client/tests/serve.js &
|
- DEBUG=* node socketIO_client/tests/serve.js &
|
||||||
- DEBUG=* node socketIO_client/tests/proxy.js &
|
|
||||||
- sleep 3
|
- sleep 3
|
||||||
script: nosetests
|
script: nosetests
|
||||||
|
|
|
||||||
19
CHANGES.rst
19
CHANGES.rst
|
|
@ -1,7 +1,26 @@
|
||||||
|
0.6.5
|
||||||
|
-----
|
||||||
|
- Updated wait loop to be more responsive under websocket transport
|
||||||
|
|
||||||
|
0.6.4
|
||||||
|
-----
|
||||||
|
- Fixed support for Python 3
|
||||||
|
- Fixed thread cleanup
|
||||||
|
|
||||||
|
0.6.3
|
||||||
|
-----
|
||||||
|
- Upgraded to socket.io protocol 1.x for websocket transport
|
||||||
|
- Added locks to fix concurrency issues with polling transport
|
||||||
|
- Fixed SSL support
|
||||||
|
|
||||||
0.6.1
|
0.6.1
|
||||||
-----
|
-----
|
||||||
- Upgraded to socket.io protocol 1.x thanks to Sean Arietta and Joe Palmer
|
- Upgraded to socket.io protocol 1.x thanks to Sean Arietta and Joe Palmer
|
||||||
|
|
||||||
|
0.5.6
|
||||||
|
-----
|
||||||
|
- Backported to support requests 0.8.2
|
||||||
|
|
||||||
0.5.5
|
0.5.5
|
||||||
-----
|
-----
|
||||||
- Fixed reconnection in the event of server restart
|
- Fixed reconnection in the event of server restart
|
||||||
|
|
|
||||||
18
LICENSE
18
LICENSE
|
|
@ -1,7 +1,19 @@
|
||||||
Copyright (c) 2013 Roy Hyunjin Han and contributors
|
Copyright (c) 2013 Roy Hyunjin Han and contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
|
||||||
12
README.rst
12
README.rst
|
|
@ -6,6 +6,8 @@ socketIO-client
|
||||||
===============
|
===============
|
||||||
Here is a `socket.io <http://socket.io>`_ client library for Python. You can use it to write test code for your socket.io server.
|
Here is a `socket.io <http://socket.io>`_ client library for Python. You can use it to write test code for your socket.io server.
|
||||||
|
|
||||||
|
Please note that this version implements `socket.io protocol 1.x <https://github.com/automattic/socket.io-protocol>`_, which is not backwards compatible. If you want to communicate using `socket.io protocol 0.9 <https://github.com/learnboost/socket.io-spec>`_ (which is compatible with `gevent-socketio <https://github.com/abourget/gevent-socketio>`_), please use `socketIO-client 0.5.6 <https://pypi.python.org/pypi/socketIO-client/0.5.6>`_.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
@ -33,7 +35,8 @@ Activate isolated environment. ::
|
||||||
Launch your socket.io server. ::
|
Launch your socket.io server. ::
|
||||||
|
|
||||||
# Get package folder
|
# Get package folder
|
||||||
PACKAGE_FOLDER=`python -c "import os, socketIO_client; print(os.path.dirname(socketIO_client.__file__))"`
|
PACKAGE_FOLDER=`python -c "import os, socketIO_client;\
|
||||||
|
print(os.path.dirname(socketIO_client.__file__))"`
|
||||||
# Start socket.io server
|
# Start socket.io server
|
||||||
DEBUG=* node $PACKAGE_FOLDER/tests/serve.js
|
DEBUG=* node $PACKAGE_FOLDER/tests/serve.js
|
||||||
# Start proxy server in a separate terminal on the same machine
|
# Start proxy server in a separate terminal on the same machine
|
||||||
|
|
@ -135,7 +138,8 @@ Specify params, headers, cookies, proxies thanks to the `requests <http://python
|
||||||
from socketIO_client import SocketIO
|
from socketIO_client import SocketIO
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
SocketIO('localhost', 8000,
|
SocketIO(
|
||||||
|
localhost', 8000,
|
||||||
params={'q': 'qqq'},
|
params={'q': 'qqq'},
|
||||||
headers={'Authorization': 'Basic ' + b64encode('username:password')},
|
headers={'Authorization': 'Basic ' + b64encode('username:password')},
|
||||||
cookies={'a': 'aaa'},
|
cookies={'a': 'aaa'},
|
||||||
|
|
@ -156,11 +160,11 @@ This software is available under the MIT License.
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
-------
|
-------
|
||||||
- `Guillermo Rauch <https://github.com/rauchg>`_ wrote the `socket.io specification <https://github.com/LearnBoost/socket.io-spec>`_.
|
- `Guillermo Rauch <https://github.com/rauchg>`_ wrote the `socket.io specification <https://github.com/automattic/socket.io-protocol>`_.
|
||||||
- `Hiroki Ohtani <https://github.com/liris>`_ wrote `websocket-client <https://github.com/liris/websocket-client>`_.
|
- `Hiroki Ohtani <https://github.com/liris>`_ wrote `websocket-client <https://github.com/liris/websocket-client>`_.
|
||||||
- `rod <http://stackoverflow.com/users/370115/rod>`_ wrote a `prototype for a Python client to a socket.io server <http://stackoverflow.com/questions/6692908/formatting-messages-to-send-to-socket-io-node-js-server-from-python-client>`_.
|
- `rod <http://stackoverflow.com/users/370115/rod>`_ wrote a `prototype for a Python client to a socket.io server <http://stackoverflow.com/questions/6692908/formatting-messages-to-send-to-socket-io-node-js-server-from-python-client>`_.
|
||||||
- `Alexandre Bourget <https://github.com/abourget>`_ wrote `gevent-socketio <https://github.com/abourget/gevent-socketio>`_, which is a socket.io server written in Python.
|
- `Alexandre Bourget <https://github.com/abourget>`_ wrote `gevent-socketio <https://github.com/abourget/gevent-socketio>`_, which is a socket.io server written in Python.
|
||||||
- `Paul Kienzle <https://github.com/pkienzle>`_, `Zac Lee <https://github.com/zratic>`_, `Josh VanderLinden <https://github.com/codekoala>`_, `Ian Fitzpatrick <https://github.com/ifitzpatrick>`_, `Lucas Klein <https://github.com/lukasklein>`_, `Rui Chicoria <https://github.com/rchicoria>`_, `Travis Odom <https://github.com/burstaholic>`_, `Patrick Huber <https://github.com/stackmagic>`_, `Brad Campbell <https://github.com/bradjc>`_, `Daniel <https://github.com/dabidan>`_, `Sean Arietta <https://github.com/sarietta>`_ submitted code to expand support of the socket.io protocol.
|
- `Paul Kienzle <https://github.com/pkienzle>`_, `Zac Lee <https://github.com/zratic>`_, `Josh VanderLinden <https://github.com/codekoala>`_, `Ian Fitzpatrick <https://github.com/ifitzpatrick>`_, `Lucas Klein <https://github.com/lukasklein>`_, `Rui Chicoria <https://github.com/rchicoria>`_, `Travis Odom <https://github.com/burstaholic>`_, `Patrick Huber <https://github.com/stackmagic>`_, `Brad Campbell <https://github.com/bradjc>`_, `Daniel <https://github.com/dabidan>`_, `Sean Arietta <https://github.com/sarietta>`_ submitted code to expand support of the socket.io protocol.
|
||||||
- `Bernard Pratz <https://github.com/guyzmo>`_, `Francis Bull <https://github.com/franbull>`_ wrote prototypes to support xhr-polling and jsonp-polling.
|
- `Bernard Pratz <https://github.com/guyzmo>`_, `Francis Bull <https://github.com/franbull>`_ wrote prototypes to support xhr-polling and jsonp-polling.
|
||||||
- `Eric Chen <https://github.com/taiyangc>`_, `Denis Zinevich <https://github.com/dzinevich>`_, `Thiago Hersan <https://github.com/thiagohersan>`_, `Nayef Copty <https://github.com/nayefc>`_, `Jörgen Karlsson <https://github.com/jorgen-k>`_, `Branden Ghena <https://github.com/brghena>`_ suggested ways to make the connection more robust.
|
- `Eric Chen <https://github.com/taiyangc>`_, `Denis Zinevich <https://github.com/dzinevich>`_, `Thiago Hersan <https://github.com/thiagohersan>`_, `Nayef Copty <https://github.com/nayefc>`_, `Jörgen Karlsson <https://github.com/jorgen-k>`_, `Branden Ghena <https://github.com/brghena>`_, `Tim Landscheidt <https://github.com/scfc>`_, `Matt Porritt <https://github.com/mattporritt>`_ suggested ways to make the connection more robust.
|
||||||
- `Merlijn van Deen <https://github.com/valhallasw>`_, `Frederic Sureau <https://github.com/fredericsureau>`_, `Marcus Cobden <https://github.com/leth>`_, `Drew Hutchison <https://github.com/drewhutchison>`_, `wuurrd <https://github.com/wuurrd>`_, `Adam Kecer <https://github.com/amfg>`_, `Alex Monk <https://github.com/Krenair>`_, `Vishal P R <https://github.com/vishalwy>`_, `John Vandenberg <https://github.com/jayvdb>`_, `Thomas Grainger <https://github.com/graingert>`_ proposed changes that make the library more friendly and practical for you!
|
- `Merlijn van Deen <https://github.com/valhallasw>`_, `Frederic Sureau <https://github.com/fredericsureau>`_, `Marcus Cobden <https://github.com/leth>`_, `Drew Hutchison <https://github.com/drewhutchison>`_, `wuurrd <https://github.com/wuurrd>`_, `Adam Kecer <https://github.com/amfg>`_, `Alex Monk <https://github.com/Krenair>`_, `Vishal P R <https://github.com/vishalwy>`_, `John Vandenberg <https://github.com/jayvdb>`_, `Thomas Grainger <https://github.com/graingert>`_ proposed changes that make the library more friendly and practical for you!
|
||||||
|
|
|
||||||
57
TODO.goals
57
TODO.goals
|
|
@ -1,9 +1,48 @@
|
||||||
Add Websocket transport
|
= Consider supporting both protocols in the same library
|
||||||
Update proxy to include websocket depending on argument
|
https://github.com/invisibleroads/socketIO-client/issues/95
|
||||||
Use prepared request to get headers from http_session
|
Add binary support
|
||||||
Include https://github.com/invisibleroads/socketIO-client/issues/68
|
https://github.com/invisibleroads/socketIO-client/pull/85
|
||||||
Add test for on_reconnect using sarietta's bash scripts
|
https://github.com/invisibleroads/socketIO-client/issues/70
|
||||||
Consider logging packets sent and received
|
https://github.com/invisibleroads/socketIO-client/issues/71
|
||||||
Implement rooms #65
|
https://github.com/invisibleroads/socketIO-client/issues/91
|
||||||
Implement binary event
|
Check requests dependency
|
||||||
Implement binary ack
|
https://github.com/invisibleroads/socketIO-client/issues/92
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/93
|
||||||
|
https://github.com/invisibleroads/socketIO-client/commit/b288d89c15d452a30bfeb00f38494f59f71f5a43
|
||||||
|
Check SSL
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/86
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/87
|
||||||
|
Look into invalid namespace handling
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/84
|
||||||
|
Check unicode issues
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/81
|
||||||
|
Check python3 support for socketIO-client 0.5.6
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/83
|
||||||
|
Check OK assertion
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/99
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/103
|
||||||
|
Check why connected=True after termination
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/80
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/98
|
||||||
|
Consider catching heartbeat thread exception
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/100
|
||||||
|
Check why it blocks when defining a namespace
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/96
|
||||||
|
Check why transports are not being set
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/102
|
||||||
|
Look at 404 not found error
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/101
|
||||||
|
Look at socketio off and socketio once
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/94
|
||||||
|
Implement rooms
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/72
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/65
|
||||||
|
Check tests
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/90
|
||||||
|
Check whether it works on Windows 8
|
||||||
|
https://github.com/invisibleroads/socketIO-client/issues/97
|
||||||
|
Add debian packaging support
|
||||||
|
https://github.com/invisibleroads/socketIO-client/pull/89
|
||||||
|
|
||||||
|
+ Review issues and pull requests
|
||||||
|
+ Order issues
|
||||||
|
|
|
||||||
7
setup.py
7
setup.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import io
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, join
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
|
@ -10,13 +11,14 @@ REQUIREMENTS = [
|
||||||
|
|
||||||
|
|
||||||
HERE = dirname(abspath(__file__))
|
HERE = dirname(abspath(__file__))
|
||||||
DESCRIPTION = '\n\n'.join(open(join(HERE, _)).read() for _ in [
|
LOAD_TEXT = lambda name: io.open(join(HERE, name), encoding='UTF-8').read()
|
||||||
|
DESCRIPTION = '\n\n'.join(LOAD_TEXT(_) for _ in [
|
||||||
'README.rst',
|
'README.rst',
|
||||||
'CHANGES.rst',
|
'CHANGES.rst',
|
||||||
])
|
])
|
||||||
setup(
|
setup(
|
||||||
name='socketIO_client',
|
name='socketIO_client',
|
||||||
version='0.6.1',
|
version='0.6.5',
|
||||||
description='A socket.io client library',
|
description='A socket.io client library',
|
||||||
long_description=DESCRIPTION,
|
long_description=DESCRIPTION,
|
||||||
license='MIT',
|
license='MIT',
|
||||||
|
|
@ -24,6 +26,7 @@ setup(
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
],
|
],
|
||||||
keywords='socket.io node.js',
|
keywords='socket.io node.js',
|
||||||
author='Roy Hyunjin Han',
|
author='Roy Hyunjin Han',
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ from .parsers import (
|
||||||
format_socketIO_packet_data, parse_socketIO_packet_data,
|
format_socketIO_packet_data, parse_socketIO_packet_data,
|
||||||
get_namespace_path)
|
get_namespace_path)
|
||||||
from .symmetries import get_character
|
from .symmetries import get_character
|
||||||
from .transports import XHR_PollingTransport, prepare_http_session, TRANSPORTS
|
from .transports import (
|
||||||
|
WebsocketTransport, XHR_PollingTransport, prepare_http_session, TRANSPORTS)
|
||||||
|
|
||||||
|
|
||||||
__all__ = 'SocketIO', 'SocketIONamespace'
|
__all__ = 'SocketIO', 'SocketIONamespace'
|
||||||
__version__ = '0.6.1'
|
__version__ = '0.6.3'
|
||||||
BaseNamespace = SocketIONamespace
|
BaseNamespace = SocketIONamespace
|
||||||
LoggingNamespace = LoggingSocketIONamespace
|
LoggingNamespace = LoggingSocketIONamespace
|
||||||
|
|
||||||
|
|
@ -39,10 +40,12 @@ class EngineIO(LoggingMixin):
|
||||||
self._wait_for_connection = wait_for_connection
|
self._wait_for_connection = wait_for_connection
|
||||||
self._client_transports = transports
|
self._client_transports = transports
|
||||||
self._hurry_interval_in_seconds = hurry_interval_in_seconds
|
self._hurry_interval_in_seconds = hurry_interval_in_seconds
|
||||||
self._kw = kw
|
self._http_session = prepare_http_session(kw)
|
||||||
|
|
||||||
self._log_name = self._url
|
self._log_name = self._url
|
||||||
self._wants_to_close = False
|
self._wants_to_close = False
|
||||||
self._opened = False
|
self._opened = False
|
||||||
|
|
||||||
if Namespace:
|
if Namespace:
|
||||||
self.define(Namespace)
|
self.define(Namespace)
|
||||||
self._transport
|
self._transport
|
||||||
|
|
@ -54,7 +57,7 @@ class EngineIO(LoggingMixin):
|
||||||
if self._opened:
|
if self._opened:
|
||||||
return self._transport_instance
|
return self._transport_instance
|
||||||
self._engineIO_session = self._get_engineIO_session()
|
self._engineIO_session = self._get_engineIO_session()
|
||||||
self._transport_instance = self._negotiate_transport()
|
self._negotiate_transport()
|
||||||
self._connect_namespaces()
|
self._connect_namespaces()
|
||||||
self._opened = True
|
self._opened = True
|
||||||
self._reset_heartbeat()
|
self._reset_heartbeat()
|
||||||
|
|
@ -62,7 +65,6 @@ class EngineIO(LoggingMixin):
|
||||||
|
|
||||||
def _get_engineIO_session(self):
|
def _get_engineIO_session(self):
|
||||||
warning_screen = self._yield_warning_screen()
|
warning_screen = self._yield_warning_screen()
|
||||||
self._http_session = prepare_http_session(self._kw)
|
|
||||||
for elapsed_time in warning_screen:
|
for elapsed_time in warning_screen:
|
||||||
transport = XHR_PollingTransport(
|
transport = XHR_PollingTransport(
|
||||||
self._http_session, self._is_secure, self._url)
|
self._http_session, self._is_secure, self._url)
|
||||||
|
|
@ -75,36 +77,58 @@ class EngineIO(LoggingMixin):
|
||||||
raise
|
raise
|
||||||
warning = Exception('[waiting for connection] %s' % e)
|
warning = Exception('[waiting for connection] %s' % e)
|
||||||
warning_screen.throw(warning)
|
warning_screen.throw(warning)
|
||||||
assert engineIO_packet_type == 0
|
assert engineIO_packet_type == 0 # engineIO_packet_type == open
|
||||||
return parse_engineIO_session(engineIO_packet_data)
|
return parse_engineIO_session(engineIO_packet_data)
|
||||||
|
|
||||||
def _negotiate_transport(self):
|
def _negotiate_transport(self):
|
||||||
self._transport_name = 'xhr-polling'
|
self._transport_instance = self._get_transport('xhr-polling')
|
||||||
return self._get_transport(self._transport_name)
|
self.transport_name = 'xhr-polling'
|
||||||
|
is_ws_client = 'websocket' in self._client_transports
|
||||||
|
is_ws_server = 'websocket' in self._engineIO_session.transport_upgrades
|
||||||
|
if is_ws_client and is_ws_server:
|
||||||
|
try:
|
||||||
|
transport = self._get_transport('websocket')
|
||||||
|
transport.send_packet(2, 'probe')
|
||||||
|
for packet_type, packet_data in transport.recv_packet():
|
||||||
|
if packet_type == 3 and packet_data == b'probe':
|
||||||
|
transport.send_packet(5, '')
|
||||||
|
self._transport_instance = transport
|
||||||
|
self.transport_name = 'websocket'
|
||||||
|
else:
|
||||||
|
self._warn('unexpected engine.io packet')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._debug('[transport selected] %s', self.transport_name)
|
||||||
|
|
||||||
def _reset_heartbeat(self):
|
def _reset_heartbeat(self):
|
||||||
try:
|
try:
|
||||||
self._heartbeat_thread.halt()
|
self._heartbeat_thread.halt()
|
||||||
|
hurried = self._heartbeat_thread.hurried
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
hurried = False
|
||||||
ping_interval = self._engineIO_session.ping_interval
|
ping_interval = self._engineIO_session.ping_interval
|
||||||
if self._transport_name.endswith('-polling'):
|
if self.transport_name.endswith('-polling'):
|
||||||
|
# Use ping/pong to unblock recv for polling transport
|
||||||
hurry_interval_in_seconds = self._hurry_interval_in_seconds
|
hurry_interval_in_seconds = self._hurry_interval_in_seconds
|
||||||
else:
|
else:
|
||||||
|
# Use timeout to unblock recv for websocket transport
|
||||||
hurry_interval_in_seconds = ping_interval
|
hurry_interval_in_seconds = ping_interval
|
||||||
self._heartbeat_thread = HeartbeatThread(
|
self._heartbeat_thread = HeartbeatThread(
|
||||||
send_heartbeat=self._ping,
|
send_heartbeat=self._ping,
|
||||||
relax_interval_in_seconds=ping_interval,
|
relax_interval_in_seconds=ping_interval,
|
||||||
hurry_interval_in_seconds=hurry_interval_in_seconds)
|
hurry_interval_in_seconds=hurry_interval_in_seconds)
|
||||||
self._heartbeat_thread.start()
|
self._heartbeat_thread.start()
|
||||||
|
if hurried:
|
||||||
|
self._heartbeat_thread.hurry()
|
||||||
|
self._debug('[heartbeat reset]')
|
||||||
|
|
||||||
def _connect_namespaces(self):
|
def _connect_namespaces(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _get_transport(self, transport_name):
|
def _get_transport(self, transport_name):
|
||||||
self._debug('[transport selected] %s', transport_name)
|
|
||||||
SelectedTransport = {
|
SelectedTransport = {
|
||||||
'xhr-polling': XHR_PollingTransport,
|
'xhr-polling': XHR_PollingTransport,
|
||||||
|
'websocket': WebsocketTransport,
|
||||||
}[transport_name]
|
}[transport_name]
|
||||||
return SelectedTransport(
|
return SelectedTransport(
|
||||||
self._http_session, self._is_secure, self._url,
|
self._http_session, self._is_secure, self._url,
|
||||||
|
|
@ -143,51 +167,62 @@ class EngineIO(LoggingMixin):
|
||||||
def send(self, engineIO_packet_data):
|
def send(self, engineIO_packet_data):
|
||||||
self._message(engineIO_packet_data)
|
self._message(engineIO_packet_data)
|
||||||
|
|
||||||
@retry
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
engineIO_packet_type = 0
|
engineIO_packet_type = 0
|
||||||
self._transport.send_packet(engineIO_packet_type, '')
|
self._transport_instance.send_packet(engineIO_packet_type)
|
||||||
|
|
||||||
def _close(self):
|
def _close(self):
|
||||||
self._wants_to_close = True
|
self._wants_to_close = True
|
||||||
self._heartbeat_thread.halt()
|
try:
|
||||||
|
self._heartbeat_thread.halt()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
if not self._opened:
|
if not self._opened:
|
||||||
return
|
return
|
||||||
engineIO_packet_type = 1
|
engineIO_packet_type = 1
|
||||||
self._transport.send_packet(engineIO_packet_type, '')
|
try:
|
||||||
|
self._transport_instance.send_packet(engineIO_packet_type)
|
||||||
|
except (TimeoutError, ConnectionError):
|
||||||
|
pass
|
||||||
self._opened = False
|
self._opened = False
|
||||||
|
|
||||||
@retry
|
|
||||||
def _ping(self, engineIO_packet_data=''):
|
def _ping(self, engineIO_packet_data=''):
|
||||||
engineIO_packet_type = 2
|
engineIO_packet_type = 2
|
||||||
self._transport.send_packet(engineIO_packet_type, engineIO_packet_data)
|
self._transport_instance.send_packet(
|
||||||
|
engineIO_packet_type, engineIO_packet_data)
|
||||||
|
|
||||||
@retry
|
|
||||||
def _pong(self, engineIO_packet_data=''):
|
def _pong(self, engineIO_packet_data=''):
|
||||||
engineIO_packet_type = 3
|
engineIO_packet_type = 3
|
||||||
self._transport.send_packet(engineIO_packet_type, engineIO_packet_data)
|
self._transport_instance.send_packet(
|
||||||
|
engineIO_packet_type, engineIO_packet_data)
|
||||||
|
|
||||||
@retry
|
@retry
|
||||||
def _message(self, engineIO_packet_data):
|
def _message(self, engineIO_packet_data, with_transport_instance=False):
|
||||||
engineIO_packet_type = 4
|
engineIO_packet_type = 4
|
||||||
self._transport.send_packet(engineIO_packet_type, engineIO_packet_data)
|
if with_transport_instance:
|
||||||
|
transport = self._transport_instance
|
||||||
|
else:
|
||||||
|
transport = self._transport
|
||||||
|
transport.send_packet(engineIO_packet_type, engineIO_packet_data)
|
||||||
self._debug('[socket.io packet sent] %s', engineIO_packet_data)
|
self._debug('[socket.io packet sent] %s', engineIO_packet_data)
|
||||||
|
|
||||||
@retry
|
|
||||||
def _upgrade(self):
|
def _upgrade(self):
|
||||||
engineIO_packet_type = 5
|
engineIO_packet_type = 5
|
||||||
self._transport.send_packet(engineIO_packet_type, '')
|
self._transport_instance.send_packet(engineIO_packet_type)
|
||||||
|
|
||||||
@retry
|
|
||||||
def _noop(self):
|
def _noop(self):
|
||||||
engineIO_packet_type = 6
|
engineIO_packet_type = 6
|
||||||
self._transport.send_packet(engineIO_packet_type, '')
|
self._transport_instance.send_packet(engineIO_packet_type)
|
||||||
|
|
||||||
# React
|
# React
|
||||||
|
|
||||||
def wait(self, seconds=None, **kw):
|
def wait(self, seconds=None, **kw):
|
||||||
'Wait in a loop and react to events as defined in the namespaces'
|
'Wait in a loop and react to events as defined in the namespaces'
|
||||||
|
# Use ping/pong to unblock recv for polling transport
|
||||||
self._heartbeat_thread.hurry()
|
self._heartbeat_thread.hurry()
|
||||||
|
# Use timeout to unblock recv for websocket transport
|
||||||
|
self._transport.set_timeout(seconds=1)
|
||||||
|
# Listen
|
||||||
warning_screen = self._yield_warning_screen(seconds)
|
warning_screen = self._yield_warning_screen(seconds)
|
||||||
for elapsed_time in warning_screen:
|
for elapsed_time in warning_screen:
|
||||||
if self._should_stop_waiting(**kw):
|
if self._should_stop_waiting(**kw):
|
||||||
|
|
@ -198,6 +233,7 @@ class EngineIO(LoggingMixin):
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
|
self._opened = False
|
||||||
try:
|
try:
|
||||||
warning = Exception('[connection error] %s' % e)
|
warning = Exception('[connection error] %s' % e)
|
||||||
warning_screen.throw(warning)
|
warning_screen.throw(warning)
|
||||||
|
|
@ -209,6 +245,7 @@ class EngineIO(LoggingMixin):
|
||||||
except PacketError:
|
except PacketError:
|
||||||
pass
|
pass
|
||||||
self._heartbeat_thread.relax()
|
self._heartbeat_thread.relax()
|
||||||
|
self._transport.set_timeout()
|
||||||
|
|
||||||
def _should_stop_waiting(self):
|
def _should_stop_waiting(self):
|
||||||
return self._wants_to_close
|
return self._wants_to_close
|
||||||
|
|
@ -237,31 +274,31 @@ class EngineIO(LoggingMixin):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise PacketError(
|
raise PacketError(
|
||||||
'unexpected engine.io packet type (%s)' % engineIO_packet_type)
|
'unexpected engine.io packet type (%s)' % engineIO_packet_type)
|
||||||
delegate(engineIO_packet_data, namespace._find_packet_callback)
|
delegate(engineIO_packet_data, namespace)
|
||||||
if engineIO_packet_type is 4:
|
if engineIO_packet_type is 4:
|
||||||
return engineIO_packet_data
|
return engineIO_packet_data
|
||||||
|
|
||||||
def _on_open(self, data, find_packet_callback):
|
def _on_open(self, data, namespace):
|
||||||
find_packet_callback('open')()
|
namespace._find_packet_callback('open')()
|
||||||
|
|
||||||
def _on_close(self, data, find_packet_callback):
|
def _on_close(self, data, namespace):
|
||||||
find_packet_callback('close')()
|
namespace._find_packet_callback('close')()
|
||||||
|
|
||||||
def _on_ping(self, data, find_packet_callback):
|
def _on_ping(self, data, namespace):
|
||||||
self._pong(data)
|
self._pong(data)
|
||||||
find_packet_callback('ping')(data)
|
namespace._find_packet_callback('ping')(data)
|
||||||
|
|
||||||
def _on_pong(self, data, find_packet_callback):
|
def _on_pong(self, data, namespace):
|
||||||
find_packet_callback('pong')(data)
|
namespace._find_packet_callback('pong')(data)
|
||||||
|
|
||||||
def _on_message(self, data, find_packet_callback):
|
def _on_message(self, data, namespace):
|
||||||
find_packet_callback('message')(data)
|
namespace._find_packet_callback('message')(data)
|
||||||
|
|
||||||
def _on_upgrade(self, data, find_packet_callback):
|
def _on_upgrade(self, data, namespace):
|
||||||
find_packet_callback('upgrade')()
|
namespace._find_packet_callback('upgrade')()
|
||||||
|
|
||||||
def _on_noop(self, data, find_packet_callback):
|
def _on_noop(self, data, namespace):
|
||||||
find_packet_callback('noop')()
|
namespace._find_packet_callback('noop')()
|
||||||
|
|
||||||
|
|
||||||
class SocketIO(EngineIO):
|
class SocketIO(EngineIO):
|
||||||
|
|
@ -303,7 +340,7 @@ class SocketIO(EngineIO):
|
||||||
for path, namespace in self._namespace_by_path.items():
|
for path, namespace in self._namespace_by_path.items():
|
||||||
namespace._transport = self._transport_instance
|
namespace._transport = self._transport_instance
|
||||||
if path:
|
if path:
|
||||||
self.connect(path)
|
self.connect(path, with_transport_instance=True)
|
||||||
|
|
||||||
def __exit__(self, *exception_pack):
|
def __exit__(self, *exception_pack):
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
@ -316,9 +353,10 @@ class SocketIO(EngineIO):
|
||||||
# Define
|
# Define
|
||||||
|
|
||||||
def define(self, Namespace, path=''):
|
def define(self, Namespace, path=''):
|
||||||
|
self._namespace_by_path[path] = namespace = Namespace(self, path)
|
||||||
if path:
|
if path:
|
||||||
self.connect(path)
|
self.connect(path)
|
||||||
self._namespace_by_path[path] = namespace = Namespace(self, path)
|
self.wait(for_connect=True)
|
||||||
return namespace
|
return namespace
|
||||||
|
|
||||||
def on(self, event, callback, path=''):
|
def on(self, event, callback, path=''):
|
||||||
|
|
@ -336,20 +374,23 @@ class SocketIO(EngineIO):
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
|
||||||
def connect(self, path):
|
def connect(self, path, with_transport_instance=False):
|
||||||
socketIO_packet_type = 0
|
socketIO_packet_type = 0
|
||||||
socketIO_packet_data = format_socketIO_packet_data(path)
|
socketIO_packet_data = format_socketIO_packet_data(path)
|
||||||
self._message(str(socketIO_packet_type) + socketIO_packet_data)
|
self._message(
|
||||||
|
str(socketIO_packet_type) + socketIO_packet_data,
|
||||||
|
with_transport_instance)
|
||||||
|
|
||||||
def disconnect(self, path=''):
|
def disconnect(self, path=''):
|
||||||
if not self._opened:
|
if not path or not self._opened:
|
||||||
return
|
self._close()
|
||||||
if path:
|
elif path:
|
||||||
socketIO_packet_type = 1
|
socketIO_packet_type = 1
|
||||||
socketIO_packet_data = format_socketIO_packet_data(path)
|
socketIO_packet_data = format_socketIO_packet_data(path)
|
||||||
self._message(str(socketIO_packet_type) + socketIO_packet_data)
|
try:
|
||||||
else:
|
self._message(str(socketIO_packet_type) + socketIO_packet_data)
|
||||||
self._close()
|
except (TimeoutError, ConnectionError):
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
namespace = self._namespace_by_path.pop(path)
|
namespace = self._namespace_by_path.pop(path)
|
||||||
namespace.on_disconnect()
|
namespace.on_disconnect()
|
||||||
|
|
@ -379,13 +420,17 @@ class SocketIO(EngineIO):
|
||||||
|
|
||||||
# React
|
# React
|
||||||
|
|
||||||
def wait(self, seconds=None, for_callbacks=False):
|
|
||||||
super(SocketIO, self).wait(seconds, for_callbacks=for_callbacks)
|
|
||||||
|
|
||||||
def wait_for_callbacks(self, seconds=None):
|
def wait_for_callbacks(self, seconds=None):
|
||||||
self.wait(seconds, for_callbacks=True)
|
self.wait(seconds, for_callbacks=True)
|
||||||
|
|
||||||
def _should_stop_waiting(self, for_callbacks):
|
def _should_stop_waiting(self, for_connect=False, for_callbacks=False):
|
||||||
|
if for_connect:
|
||||||
|
for namespace in self._namespace_by_path.values():
|
||||||
|
is_namespace_connected = getattr(
|
||||||
|
namespace, '_connected', False)
|
||||||
|
if not is_namespace_connected:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
if for_callbacks and not self._has_ack_callback:
|
if for_callbacks and not self._has_ack_callback:
|
||||||
return True
|
return True
|
||||||
return super(SocketIO, self)._should_stop_waiting()
|
return super(SocketIO, self)._should_stop_waiting()
|
||||||
|
|
@ -413,16 +458,18 @@ class SocketIO(EngineIO):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise PacketError(
|
raise PacketError(
|
||||||
'unexpected socket.io packet type (%s)' % socketIO_packet_type)
|
'unexpected socket.io packet type (%s)' % socketIO_packet_type)
|
||||||
delegate(socketIO_packet_data, namespace._find_packet_callback)
|
delegate(socketIO_packet_data, namespace)
|
||||||
return socketIO_packet_data
|
return socketIO_packet_data
|
||||||
|
|
||||||
def _on_connect(self, data, find_packet_callback):
|
def _on_connect(self, data, namespace):
|
||||||
find_packet_callback('connect')()
|
namespace._connected = True
|
||||||
|
namespace._find_packet_callback('connect')()
|
||||||
|
|
||||||
def _on_disconnect(self, data, find_packet_callback):
|
def _on_disconnect(self, data, namespace):
|
||||||
find_packet_callback('disconnect')()
|
namespace._connected = False
|
||||||
|
namespace._find_packet_callback('disconnect')()
|
||||||
|
|
||||||
def _on_event(self, data, find_packet_callback):
|
def _on_event(self, data, namespace):
|
||||||
data_parsed = parse_socketIO_packet_data(data)
|
data_parsed = parse_socketIO_packet_data(data)
|
||||||
args = data_parsed.args
|
args = data_parsed.args
|
||||||
try:
|
try:
|
||||||
|
|
@ -432,9 +479,9 @@ class SocketIO(EngineIO):
|
||||||
if data_parsed.ack_id is not None:
|
if data_parsed.ack_id is not None:
|
||||||
args.append(self._prepare_to_send_ack(
|
args.append(self._prepare_to_send_ack(
|
||||||
data_parsed.path, data_parsed.ack_id))
|
data_parsed.path, data_parsed.ack_id))
|
||||||
find_packet_callback(event)(*args)
|
namespace._find_packet_callback(event)(*args)
|
||||||
|
|
||||||
def _on_ack(self, data, find_packet_callback):
|
def _on_ack(self, data, namespace):
|
||||||
data_parsed = parse_socketIO_packet_data(data)
|
data_parsed = parse_socketIO_packet_data(data)
|
||||||
try:
|
try:
|
||||||
ack_callback = self._get_ack_callback(data_parsed.ack_id)
|
ack_callback = self._get_ack_callback(data_parsed.ack_id)
|
||||||
|
|
@ -442,13 +489,13 @@ class SocketIO(EngineIO):
|
||||||
return
|
return
|
||||||
ack_callback(*data_parsed.args)
|
ack_callback(*data_parsed.args)
|
||||||
|
|
||||||
def _on_error(self, data, find_packet_callback):
|
def _on_error(self, data, namespace):
|
||||||
find_packet_callback('error')(data)
|
namespace._find_packet_callback('error')(data)
|
||||||
|
|
||||||
def _on_binary_event(self, data, find_packet_callback):
|
def _on_binary_event(self, data, namespace):
|
||||||
self._warn('[not implemented] binary event')
|
self._warn('[not implemented] binary event')
|
||||||
|
|
||||||
def _on_binary_ack(self, data, find_packet_callback):
|
def _on_binary_ack(self, data, namespace):
|
||||||
self._warn('[not implemented] binary ack')
|
self._warn('[not implemented] binary ack')
|
||||||
|
|
||||||
def _prepare_to_send_ack(self, path, ack_id):
|
def _prepare_to_send_ack(self, path, ack_id):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
|
||||||
from .exceptions import ConnectionError, TimeoutError
|
from .exceptions import ConnectionError, TimeoutError
|
||||||
|
|
@ -32,7 +33,7 @@ class HeartbeatThread(Thread):
|
||||||
interval_in_seconds = self._relax_interval_in_seconds
|
interval_in_seconds = self._relax_interval_in_seconds
|
||||||
self._rest.wait(interval_in_seconds)
|
self._rest.wait(interval_in_seconds)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
pass
|
logging.debug('[heartbeat connection error]')
|
||||||
|
|
||||||
def relax(self):
|
def relax(self):
|
||||||
self._adrenaline.clear()
|
self._adrenaline.clear()
|
||||||
|
|
@ -42,6 +43,10 @@ class HeartbeatThread(Thread):
|
||||||
self._rest.set()
|
self._rest.set()
|
||||||
self._rest.clear()
|
self._rest.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hurried(self):
|
||||||
|
return self._adrenaline.is_set()
|
||||||
|
|
||||||
def halt(self):
|
def halt(self):
|
||||||
self._rest.set()
|
self._rest.set()
|
||||||
self._halt.set()
|
self._halt.set()
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ def _yield_elapsed_time(seconds=None):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
if seconds is None:
|
if seconds is None:
|
||||||
while True:
|
while True:
|
||||||
yield time.time() - start_time
|
yield _get_elapsed_time(start_time)
|
||||||
while time.time() - start_time < seconds:
|
while _get_elapsed_time(start_time) < seconds:
|
||||||
yield time.time() - start_time
|
yield _get_elapsed_time(start_time)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_elapsed_time(start_time):
|
||||||
|
return time.time() - start_time
|
||||||
|
|
|
||||||
|
|
@ -141,31 +141,31 @@ class SocketIONamespace(EngineIONamespace):
|
||||||
class LoggingEngineIONamespace(EngineIONamespace):
|
class LoggingEngineIONamespace(EngineIONamespace):
|
||||||
|
|
||||||
def on_open(self):
|
def on_open(self):
|
||||||
self._debug('[open]')
|
self._debug('[engine.io open]')
|
||||||
super(LoggingEngineIONamespace, self).on_open()
|
super(LoggingEngineIONamespace, self).on_open()
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self._debug('[close]')
|
self._debug('[engine.io close]')
|
||||||
super(LoggingEngineIONamespace, self).on_close()
|
super(LoggingEngineIONamespace, self).on_close()
|
||||||
|
|
||||||
def on_ping(self, data):
|
def on_ping(self, data):
|
||||||
self._debug('[ping] %s', data)
|
self._debug('[engine.io ping] %s', data)
|
||||||
super(LoggingEngineIONamespace, self).on_ping(data)
|
super(LoggingEngineIONamespace, self).on_ping(data)
|
||||||
|
|
||||||
def on_pong(self, data):
|
def on_pong(self, data):
|
||||||
self._debug('[pong] %s', data)
|
self._debug('[engine.io pong] %s', data)
|
||||||
super(LoggingEngineIONamespace, self).on_pong(data)
|
super(LoggingEngineIONamespace, self).on_pong(data)
|
||||||
|
|
||||||
def on_message(self, data):
|
def on_message(self, data):
|
||||||
self._debug('[message] %s', data)
|
self._debug('[engine.io message] %s', data)
|
||||||
super(LoggingEngineIONamespace, self).on_message(data)
|
super(LoggingEngineIONamespace, self).on_message(data)
|
||||||
|
|
||||||
def on_upgrade(self):
|
def on_upgrade(self):
|
||||||
self._debug('[upgrade]')
|
self._debug('[engine.io upgrade]')
|
||||||
super(LoggingEngineIONamespace, self).on_upgrade()
|
super(LoggingEngineIONamespace, self).on_upgrade()
|
||||||
|
|
||||||
def on_noop(self):
|
def on_noop(self):
|
||||||
self._debug('[noop]')
|
self._debug('[engine.io noop]')
|
||||||
super(LoggingEngineIONamespace, self).on_noop()
|
super(LoggingEngineIONamespace, self).on_noop()
|
||||||
|
|
||||||
def on_event(self, event, *args):
|
def on_event(self, event, *args):
|
||||||
|
|
@ -173,22 +173,25 @@ class LoggingEngineIONamespace(EngineIONamespace):
|
||||||
arguments = [repr(_) for _ in args]
|
arguments = [repr(_) for _ in args]
|
||||||
if callback:
|
if callback:
|
||||||
arguments.append('callback(*args)')
|
arguments.append('callback(*args)')
|
||||||
self._info('[event] %s(%s)', event, ', '.join(arguments))
|
self._info('[engine.io event] %s(%s)', event, ', '.join(arguments))
|
||||||
super(LoggingEngineIONamespace, self).on_event(event, *args)
|
super(LoggingEngineIONamespace, self).on_event(event, *args)
|
||||||
|
|
||||||
|
|
||||||
class LoggingSocketIONamespace(SocketIONamespace):
|
class LoggingSocketIONamespace(SocketIONamespace, LoggingEngineIONamespace):
|
||||||
|
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
self._debug('%s[connect]', _make_logging_header(self.path))
|
self._debug(
|
||||||
|
'%s[socket.io connect]', _make_logging_header(self.path))
|
||||||
super(LoggingSocketIONamespace, self).on_connect()
|
super(LoggingSocketIONamespace, self).on_connect()
|
||||||
|
|
||||||
def on_reconnect(self):
|
def on_reconnect(self):
|
||||||
self._debug('%s[reconnect]', _make_logging_header(self.path))
|
self._debug(
|
||||||
|
'%s[socket.io reconnect]', _make_logging_header(self.path))
|
||||||
super(LoggingSocketIONamespace, self).on_reconnect()
|
super(LoggingSocketIONamespace, self).on_reconnect()
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
self._debug('%s[disconnect]', _make_logging_header(self.path))
|
self._debug(
|
||||||
|
'%s[socket.io disconnect]', _make_logging_header(self.path))
|
||||||
super(LoggingSocketIONamespace, self).on_disconnect()
|
super(LoggingSocketIONamespace, self).on_disconnect()
|
||||||
|
|
||||||
def on_event(self, event, *args):
|
def on_event(self, event, *args):
|
||||||
|
|
@ -197,12 +200,13 @@ class LoggingSocketIONamespace(SocketIONamespace):
|
||||||
if callback:
|
if callback:
|
||||||
arguments.append('callback(*args)')
|
arguments.append('callback(*args)')
|
||||||
self._info(
|
self._info(
|
||||||
'%s[event] %s(%s)', _make_logging_header(self.path), event,
|
'%s[socket.io event] %s(%s)', _make_logging_header(self.path),
|
||||||
', '.join(arguments))
|
event, ', '.join(arguments))
|
||||||
super(LoggingSocketIONamespace, self).on_event(event, *args)
|
super(LoggingSocketIONamespace, self).on_event(event, *args)
|
||||||
|
|
||||||
def on_error(self, data):
|
def on_error(self, data):
|
||||||
self._debug('%s[error] %s', _make_logging_header(self.path), data)
|
self._debug(
|
||||||
|
'%s[socket.io error] %s', _make_logging_header(self.path), data)
|
||||||
super(LoggingSocketIONamespace, self).on_error()
|
super(LoggingSocketIONamespace, self).on_error()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ def parse_engineIO_session(engineIO_packet_data):
|
||||||
def encode_engineIO_content(engineIO_packets):
|
def encode_engineIO_content(engineIO_packets):
|
||||||
content = bytearray()
|
content = bytearray()
|
||||||
for packet_type, packet_data in engineIO_packets:
|
for packet_type, packet_data in engineIO_packets:
|
||||||
packet_string = encode_string(str(packet_type) + packet_data)
|
packet_text = format_packet_text(packet_type, packet_data)
|
||||||
content.extend(_make_packet_header(packet_string) + packet_string)
|
content.extend(_make_packet_prefix(packet_text) + packet_text)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,10 +46,10 @@ def decode_engineIO_content(content):
|
||||||
content, content_index)
|
content, content_index)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
break
|
break
|
||||||
content_index, packet_string = _read_packet_string(
|
content_index, packet_text = _read_packet_text(
|
||||||
content, content_index, packet_length)
|
content, content_index, packet_length)
|
||||||
engineIO_packet_type = int(get_character(packet_string, 0))
|
engineIO_packet_type, engineIO_packet_data = parse_packet_text(
|
||||||
engineIO_packet_data = packet_string[1:]
|
packet_text)
|
||||||
yield engineIO_packet_type, engineIO_packet_data
|
yield engineIO_packet_type, engineIO_packet_data
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,6 +85,16 @@ def parse_socketIO_packet_data(socketIO_packet_data):
|
||||||
return SocketIOData(path=path, ack_id=ack_id, args=args)
|
return SocketIOData(path=path, ack_id=ack_id, args=args)
|
||||||
|
|
||||||
|
|
||||||
|
def format_packet_text(packet_type, packet_data):
|
||||||
|
return encode_string(str(packet_type) + packet_data)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_packet_text(packet_text):
|
||||||
|
packet_type = int(get_character(packet_text, 0))
|
||||||
|
packet_data = packet_text[1:]
|
||||||
|
return packet_type, packet_data
|
||||||
|
|
||||||
|
|
||||||
def get_namespace_path(socketIO_packet_data):
|
def get_namespace_path(socketIO_packet_data):
|
||||||
if not socketIO_packet_data.startswith(b'/'):
|
if not socketIO_packet_data.startswith(b'/'):
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -98,8 +108,8 @@ def get_namespace_path(socketIO_packet_data):
|
||||||
return ''.join(parts)
|
return ''.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def _make_packet_header(packet_string):
|
def _make_packet_prefix(packet):
|
||||||
length_string = str(len(packet_string))
|
length_string = str(len(packet))
|
||||||
header_digits = bytearray([0])
|
header_digits = bytearray([0])
|
||||||
for i in range(len(length_string)):
|
for i in range(len(length_string)):
|
||||||
header_digits.append(ord(length_string[i]) - 48)
|
header_digits.append(ord(length_string[i]) - 48)
|
||||||
|
|
@ -120,8 +130,8 @@ def _read_packet_length(content, content_index):
|
||||||
return content_index, int(packet_length_string)
|
return content_index, int(packet_length_string)
|
||||||
|
|
||||||
|
|
||||||
def _read_packet_string(content, content_index, packet_length):
|
def _read_packet_text(content, content_index, packet_length):
|
||||||
while get_byte(content, content_index) == 255:
|
while get_byte(content, content_index) == 255:
|
||||||
content_index += 1
|
content_index += 1
|
||||||
packet_string = content[content_index:content_index + packet_length]
|
packet_text = content[content_index:content_index + packet_length]
|
||||||
return content_index + packet_length, packet_string
|
return content_index + packet_length, packet_text
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
import six
|
import six
|
||||||
try:
|
try:
|
||||||
from urllib.parse import urlparse as parse_url
|
from urllib import urlencode as format_query
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
from urllib.parse import urlencode as format_query
|
||||||
|
try:
|
||||||
from urlparse import urlparse as parse_url
|
from urlparse import urlparse as parse_url
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import urlparse as parse_url
|
||||||
|
try:
|
||||||
|
memoryview = memoryview
|
||||||
|
except NameError:
|
||||||
|
memoryview = buffer
|
||||||
|
|
||||||
|
|
||||||
|
def get_character(x, index):
|
||||||
|
return chr(get_byte(x, index))
|
||||||
|
|
||||||
|
|
||||||
def get_byte(x, index):
|
def get_byte(x, index):
|
||||||
return six.indexbytes(x, index)
|
return six.indexbytes(x, index)
|
||||||
|
|
||||||
|
|
||||||
def get_character(x, index):
|
|
||||||
return chr(six.indexbytes(x, index))
|
|
||||||
|
|
||||||
|
|
||||||
def encode_string(x):
|
def encode_string(x):
|
||||||
return x.encode('utf-8')
|
return x.encode('utf-8')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from .. import SocketIO, LoggingNamespace, find_callback
|
||||||
|
|
||||||
|
|
||||||
HOST = 'localhost'
|
HOST = 'localhost'
|
||||||
PORT = 8000
|
PORT = 9000
|
||||||
DATA = 'xxx'
|
DATA = 'xxx'
|
||||||
PAYLOAD = {'xxx': 'yyy'}
|
PAYLOAD = {'xxx': 'yyy'}
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
@ -17,21 +17,18 @@ class BaseMixin(object):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(BaseMixin, self).setUp()
|
super(BaseMixin, self).setUp()
|
||||||
self.called_on_response = False
|
self.called_on_response = False
|
||||||
|
self.wait_time_in_seconds = 1
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(BaseMixin, self).tearDown()
|
super(BaseMixin, self).tearDown()
|
||||||
del self.socketIO
|
self.socketIO.disconnect()
|
||||||
|
|
||||||
def test_disconnect(self):
|
def test_disconnect(self):
|
||||||
'Disconnect'
|
'Disconnect'
|
||||||
|
namespace = self.socketIO.define(Namespace)
|
||||||
self.assertTrue(self.socketIO.connected)
|
self.assertTrue(self.socketIO.connected)
|
||||||
|
self.assertFalse(namespace.called_on_disconnect)
|
||||||
self.socketIO.disconnect()
|
self.socketIO.disconnect()
|
||||||
self.assertFalse(self.socketIO.connected)
|
|
||||||
# Use context manager
|
|
||||||
with SocketIO(HOST, PORT, Namespace) as self.socketIO:
|
|
||||||
namespace = self.socketIO.get_namespace()
|
|
||||||
self.assertFalse(namespace.called_on_disconnect)
|
|
||||||
self.assertTrue(self.socketIO.connected)
|
|
||||||
self.assertTrue(namespace.called_on_disconnect)
|
self.assertTrue(namespace.called_on_disconnect)
|
||||||
self.assertFalse(self.socketIO.connected)
|
self.assertFalse(self.socketIO.connected)
|
||||||
|
|
||||||
|
|
@ -116,7 +113,7 @@ class BaseMixin(object):
|
||||||
def test_wait_with_disconnect(self):
|
def test_wait_with_disconnect(self):
|
||||||
'Exit loop when the client wants to disconnect'
|
'Exit loop when the client wants to disconnect'
|
||||||
self.socketIO.define(Namespace)
|
self.socketIO.define(Namespace)
|
||||||
self.socketIO.emit('wait_with_disconnect')
|
self.socketIO.disconnect()
|
||||||
timeout_in_seconds = 5
|
timeout_in_seconds = 5
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
self.socketIO.wait(timeout_in_seconds)
|
self.socketIO.wait(timeout_in_seconds)
|
||||||
|
|
@ -159,8 +156,17 @@ class Test_XHR_PollingTransport(BaseMixin, TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(Test_XHR_PollingTransport, self).setUp()
|
super(Test_XHR_PollingTransport, self).setUp()
|
||||||
self.socketIO = SocketIO(HOST, PORT, LoggingNamespace, transports=[
|
self.socketIO = SocketIO(HOST, PORT, LoggingNamespace, transports=[
|
||||||
'xhr-polling'])
|
'xhr-polling'], verify=False)
|
||||||
self.wait_time_in_seconds = 1
|
self.assertEqual(self.socketIO.transport_name, 'xhr-polling')
|
||||||
|
|
||||||
|
|
||||||
|
class Test_WebsocketTransport(BaseMixin, TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(Test_WebsocketTransport, self).setUp()
|
||||||
|
self.socketIO = SocketIO(HOST, PORT, LoggingNamespace, transports=[
|
||||||
|
'xhr-polling', 'websocket'], verify=False)
|
||||||
|
self.assertEqual(self.socketIO.transport_name, 'websocket')
|
||||||
|
|
||||||
|
|
||||||
class Namespace(LoggingNamespace):
|
class Namespace(LoggingNamespace):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var socket = io('http://localhost');
|
var socket = io('//localhost');
|
||||||
var chat = io('/chat');
|
var chat = io('/chat');
|
||||||
var news = io('/news');
|
var news = io('/news');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
// DEBUG=* node serve.js
|
// DEBUG=* node serve.js
|
||||||
|
|
||||||
var app = require('http').createServer(serve).listen(9000);
|
var argv = require('yargs').argv;
|
||||||
|
if (argv.secure) {
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var app = require('https').createServer({
|
||||||
|
key: fs.readFileSync(path.resolve(__dirname, 'ssl.key')),
|
||||||
|
cert: fs.readFileSync(path.resolve(__dirname, 'ssl.crt'))
|
||||||
|
}, serve);
|
||||||
|
} else {
|
||||||
|
var app = require('http').createServer(serve);
|
||||||
|
}
|
||||||
|
app.listen(9000);
|
||||||
|
|
||||||
var io = require('socket.io')(app);
|
var io = require('socket.io')(app);
|
||||||
var fs = require('fs');
|
|
||||||
var PAYLOAD = {'xxx': 'yyy'};
|
var PAYLOAD = {'xxx': 'yyy'};
|
||||||
|
|
||||||
io.on('connection', function(socket) {
|
io.on('connection', function(socket) {
|
||||||
|
|
@ -54,9 +65,6 @@ io.on('connection', function(socket) {
|
||||||
socket.on('bbb', function(payload, fn) {
|
socket.on('bbb', function(payload, fn) {
|
||||||
if (fn) fn(payload);
|
if (fn) fn(payload);
|
||||||
});
|
});
|
||||||
socket.on('wait_with_disconnect', function() {
|
|
||||||
socket.emit('wait_with_disconnect_response');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
io.of('/chat').on('connection', function(socket) {
|
io.of('/chat').on('connection', function(socket) {
|
||||||
|
|
|
||||||
21
socketIO_client/tests/ssl.crt
Normal file
21
socketIO_client/tests/ssl.crt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDZTCCAk2gAwIBAgIJAK1HKQ8zF3cCMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIDAJOWTELMAkGA1UEBwwCTlkxDDAKBgNVBAoMA1hZWjES
|
||||||
|
MBAGA1UEAwwJbG9jYWxob3N0MB4XDTE1MDQxNTE5NDUwNFoXDTE2MDQxNDE5NDUw
|
||||||
|
NFowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQswCQYDVQQHDAJOWTEMMAoG
|
||||||
|
A1UECgwDWFlaMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA
|
||||||
|
A4IBDwAwggEKAoIBAQDAQUM9+xbiDeJXg+7X6HgXwla2AnGKWbZ11hZZUYbQwHyq
|
||||||
|
ABDSqRQXVWvzac6b59/trZiJ7cQEH4+c8ln1C4qbCLvr1aWkL1BDAtSbFUFhQ2Sb
|
||||||
|
R/xkSUpq35yTuR5+oHgahDg1gbgXgPhB3Y6HoBlYMSpSUKF+INu354kxfYi0t4tP
|
||||||
|
8f309KUe6eQH3gXgTBR7pPJEUpaPOsrk6UR3cHCMqyzHulyfhgvkk5FN+EtSR9ex
|
||||||
|
dIrF6WXmfynhsAa/+bxbsgeBF9MNj3zvckCzxdQStdqOvy0mu40/7i9vwguh9cRo
|
||||||
|
HDn6lx5EaE+gSGU48UNnKX5iQdqEhprNVDj31MiJAgMBAAGjUDBOMB0GA1UdDgQW
|
||||||
|
BBRkFsPxYU+e6ZSFwmzoS45qiOzAaDAfBgNVHSMEGDAWgBRkFsPxYU+e6ZSFwmzo
|
||||||
|
S45qiOzAaDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB4JyOA5bZ3
|
||||||
|
NbkMvOjpDw+tKzcNiXZIkGfoQ8NC1DbGVhgG7Ps4VjXgUB552YSUV7iwyd3G78OC
|
||||||
|
+cxEcr+BvxXHXL2Wlxy0c/ZgBjRI5VnGbYQjjI2Iy2qJV+x5IR2oZatv45soZSLq
|
||||||
|
NFCg2KpOgcSRgs0oDGVBYO0d9m73s/kOySj2NGqVJsaQXqXtLWKnqToaCfl4Vnl+
|
||||||
|
zcMdUv8ajBZEPRg6oNi2QIvcNT8fS5gd/T4OXBa7pYuC79yOZ1X6bkKsZrcAdNGM
|
||||||
|
zO/jH6jKFjIBBx1Of+uZTzfAj/eoTu3foPuUQ+Z9NNE2nkE6SLyBSlxE7wD+SfjS
|
||||||
|
4/J0PNj22Uh3
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
socketIO_client/tests/ssl.key
Normal file
28
socketIO_client/tests/ssl.key
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAQUM9+xbiDeJX
|
||||||
|
g+7X6HgXwla2AnGKWbZ11hZZUYbQwHyqABDSqRQXVWvzac6b59/trZiJ7cQEH4+c
|
||||||
|
8ln1C4qbCLvr1aWkL1BDAtSbFUFhQ2SbR/xkSUpq35yTuR5+oHgahDg1gbgXgPhB
|
||||||
|
3Y6HoBlYMSpSUKF+INu354kxfYi0t4tP8f309KUe6eQH3gXgTBR7pPJEUpaPOsrk
|
||||||
|
6UR3cHCMqyzHulyfhgvkk5FN+EtSR9exdIrF6WXmfynhsAa/+bxbsgeBF9MNj3zv
|
||||||
|
ckCzxdQStdqOvy0mu40/7i9vwguh9cRoHDn6lx5EaE+gSGU48UNnKX5iQdqEhprN
|
||||||
|
VDj31MiJAgMBAAECggEANAFzbxC83+lhkMrfkQgRdFvdmN6QWBxsfvOql/61uUJY
|
||||||
|
dqQN6O5TwPwad33npcTTjjenS6hFndfrwUjNjLvSgp2aN/FTHVavH3FkkY7uYKEa
|
||||||
|
VebjHz20I7TZZhxtY1OFKacajV7JrZH1lduY8pccQ/8Is7ub88JvrQ+0zO5oTHnh
|
||||||
|
KEPYY5r2wLxKrzGm0NavRW9MpiHxz1vUGykvaGq9vR8dVFvZlLC5szYII+BvlII+
|
||||||
|
78XMnZbJ9ahT7dzfnzPdPPuyP3m4cdJ9c+7Advs0g2F3K/IDL3jZZCRZIaLxHIs0
|
||||||
|
PeI17teW0OmK4RWrnf6dSf0bww05x5In8GzUYgppAQKBgQD4lJVi3UmAB319CGeP
|
||||||
|
NE4cZFuMneNsuCkNEJONb8Cfsah2maM0if8tUNoR96JorjWgkUTG4oThGSQgJQw8
|
||||||
|
fPy6cW4EUhSvWCO+Q4MFFWpTcf0hBiz5O1d06FHVo39o8Ct9dv2bxJqfNtCUUf31
|
||||||
|
Fz5tvA+wvByOSazUC3AowQZ6FwKBgQDF/ksJbOBd/bu3ss7eJRjE2sXmuhfxrUiu
|
||||||
|
P5RoOEqHROAifatJk/3hwT6lx2y1g+3kJpZm9V16dNTkcuybL0yJ/VBE3uWuodrj
|
||||||
|
i9+wcg8XSnRp3BPVKzebKIKDTMdypOeb1f5yhx6cCtChRm1frKQdoXpMQqptM0jq
|
||||||
|
w3B4bryWXwKBgQCWSv+nLrPpvJ2aoyI56x3u/J59fliquw3W4FbWBOMpqnh4fJu4
|
||||||
|
gFbQRzoR8u82611xH2O9++brUhANf1jOmaMT9tDVu+rVuSyjNJ5azH/kw96PwPQg
|
||||||
|
HEjcXjpcOOYnxE4HJZJgQ5ZY/QNPKeOp88vC/RlfedyqCtF7ww6lFU+dMQKBgQC2
|
||||||
|
M7ut4sne9R8If74rZAwVLBauq1ZZi1O1NsFF33eGX/W7B9bXER+z3vfd61W4/L2x
|
||||||
|
FWmXOflaNaWsza27aZ2P5tM1bcIEIOKkQBYL9Aq7LkNPH74Ij4rOeEsStVddwy94
|
||||||
|
k0di8cFTbAhuQbdpMiCdO/qlrzvS3j0d/djEm3NlFQKBgQCpIrHaMcckCFsf2Y6o
|
||||||
|
zMnbi3859hve94OOJjauQLlw/nRE/+OaDsDN8iJoxnK0seek8ro1ixSBTScpuX8W
|
||||||
|
G2DBgqs9NrSQLe6FAckkGqVJdluoh5GewNneAcowkkauj2srnb6XtJDhFtTDY141
|
||||||
|
EPbeqGB9PUY9Ny8VzHkAb1vi6g==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -1,12 +1,32 @@
|
||||||
import requests
|
import requests
|
||||||
|
import six
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import websocket
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
from .exceptions import ConnectionError, TimeoutError
|
from .exceptions import ConnectionError, TimeoutError
|
||||||
from .parsers import decode_engineIO_content, encode_engineIO_content
|
from .parsers import (
|
||||||
|
encode_engineIO_content, decode_engineIO_content,
|
||||||
|
format_packet_text, parse_packet_text)
|
||||||
|
from .symmetries import format_query, memoryview, parse_url
|
||||||
|
|
||||||
|
|
||||||
|
if not hasattr(websocket, 'create_connection'):
|
||||||
|
sys.exit("""\
|
||||||
|
An incompatible websocket library is conflicting with the one we need.
|
||||||
|
You can remove the incompatible library and install the correct one
|
||||||
|
by running the following commands:
|
||||||
|
|
||||||
|
yes | pip uninstall websocket websocket-client
|
||||||
|
pip install -U websocket-client""")
|
||||||
|
|
||||||
|
|
||||||
ENGINEIO_PROTOCOL = 3
|
ENGINEIO_PROTOCOL = 3
|
||||||
TRANSPORTS = 'websocket', 'xhr-polling'
|
TRANSPORTS = 'xhr-polling', 'websocket'
|
||||||
|
|
||||||
|
|
||||||
class AbstractTransport(object):
|
class AbstractTransport(object):
|
||||||
|
|
@ -20,7 +40,10 @@ class AbstractTransport(object):
|
||||||
def recv_packet(self):
|
def recv_packet(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def send_packet(self, engineIO_packet_type, engineIO_packet_data):
|
def send_packet(self, engineIO_packet_type, engineIO_packet_data=''):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_timeout(self, seconds=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,56 +52,128 @@ class XHR_PollingTransport(AbstractTransport):
|
||||||
def __init__(self, http_session, is_secure, url, engineIO_session=None):
|
def __init__(self, http_session, is_secure, url, engineIO_session=None):
|
||||||
super(XHR_PollingTransport, self).__init__(
|
super(XHR_PollingTransport, self).__init__(
|
||||||
http_session, is_secure, url, engineIO_session)
|
http_session, is_secure, url, engineIO_session)
|
||||||
self.http_url = '%s://%s/' % ('https' if is_secure else 'http', url)
|
self._params = {
|
||||||
self.params = {
|
'EIO': ENGINEIO_PROTOCOL, 'transport': 'polling'}
|
||||||
'EIO': ENGINEIO_PROTOCOL,
|
|
||||||
'transport': 'polling',
|
|
||||||
}
|
|
||||||
if engineIO_session:
|
if engineIO_session:
|
||||||
self.request_index = 1
|
self._request_index = 1
|
||||||
self.kw_get = dict(timeout=engineIO_session.ping_timeout)
|
self._kw_get = dict(
|
||||||
self.kw_post = dict(headers={
|
timeout=engineIO_session.ping_timeout)
|
||||||
'content-type': 'application/octet-stream',
|
self._kw_post = dict(
|
||||||
})
|
timeout=engineIO_session.ping_timeout,
|
||||||
self.params['sid'] = engineIO_session.id
|
headers={'content-type': 'application/octet-stream'})
|
||||||
|
self._params['sid'] = engineIO_session.id
|
||||||
else:
|
else:
|
||||||
self.request_index = 0
|
self._request_index = 0
|
||||||
self.kw_get = {}
|
self._kw_get = {}
|
||||||
self.kw_post = {}
|
self._kw_post = {}
|
||||||
|
http_scheme = 'https' if is_secure else 'http'
|
||||||
|
self._http_url = '%s://%s/' % (http_scheme, url)
|
||||||
|
self._request_index_lock = threading.Lock()
|
||||||
|
self._send_packet_lock = threading.Lock()
|
||||||
|
|
||||||
def recv_packet(self):
|
def recv_packet(self):
|
||||||
params = dict(self.params)
|
params = dict(self._params)
|
||||||
params['t'] = self._get_timestamp()
|
params['t'] = self._get_timestamp()
|
||||||
response = get_response(
|
response = get_response(
|
||||||
self.http_session.get,
|
self.http_session.get,
|
||||||
self.http_url,
|
self._http_url,
|
||||||
params=params,
|
params=params,
|
||||||
**self.kw_get)
|
**self._kw_get)
|
||||||
for engineIO_packet in decode_engineIO_content(response.content):
|
for engineIO_packet in decode_engineIO_content(response.content):
|
||||||
yield engineIO_packet
|
engineIO_packet_type, engineIO_packet_data = engineIO_packet
|
||||||
|
yield engineIO_packet_type, engineIO_packet_data
|
||||||
|
|
||||||
def send_packet(self, engineIO_packet_type, engineIO_packet_data):
|
def send_packet(self, engineIO_packet_type, engineIO_packet_data=''):
|
||||||
params = dict(self.params)
|
with self._send_packet_lock:
|
||||||
params['t'] = self._get_timestamp()
|
params = dict(self._params)
|
||||||
response = get_response(
|
params['t'] = self._get_timestamp()
|
||||||
self.http_session.post,
|
data = encode_engineIO_content([
|
||||||
self.http_url,
|
|
||||||
params=params,
|
|
||||||
data=encode_engineIO_content([
|
|
||||||
(engineIO_packet_type, engineIO_packet_data),
|
(engineIO_packet_type, engineIO_packet_data),
|
||||||
]),
|
])
|
||||||
**self.kw_post)
|
response = get_response(
|
||||||
assert response.content == b'ok'
|
self.http_session.post,
|
||||||
|
self._http_url,
|
||||||
|
params=params,
|
||||||
|
data=memoryview(data),
|
||||||
|
**self._kw_post)
|
||||||
|
assert response.content == b'ok'
|
||||||
|
|
||||||
def _get_timestamp(self):
|
def _get_timestamp(self):
|
||||||
timestamp = '%s-%s' % (int(time.time() * 1000), self.request_index)
|
with self._request_index_lock:
|
||||||
self.request_index += 1
|
timestamp = '%s-%s' % (
|
||||||
|
int(time.time() * 1000), self._request_index)
|
||||||
|
self._request_index += 1
|
||||||
return timestamp
|
return timestamp
|
||||||
|
|
||||||
|
|
||||||
|
class WebsocketTransport(AbstractTransport):
|
||||||
|
|
||||||
|
def __init__(self, http_session, is_secure, url, engineIO_session=None):
|
||||||
|
super(WebsocketTransport, self).__init__(
|
||||||
|
http_session, is_secure, url, engineIO_session)
|
||||||
|
params = dict(http_session.params, **{
|
||||||
|
'EIO': ENGINEIO_PROTOCOL, 'transport': 'websocket'})
|
||||||
|
request = http_session.prepare_request(requests.Request('GET', url))
|
||||||
|
kw = {'header': ['%s: %s' % x for x in request.headers.items()]}
|
||||||
|
if engineIO_session:
|
||||||
|
params['sid'] = engineIO_session.id
|
||||||
|
kw['timeout'] = self._timeout = engineIO_session.ping_timeout
|
||||||
|
ws_url = '%s://%s/?%s' % (
|
||||||
|
'wss' if is_secure else 'ws', url, format_query(params))
|
||||||
|
http_scheme = 'https' if is_secure else 'http'
|
||||||
|
if http_scheme in http_session.proxies: # Use the correct proxy
|
||||||
|
proxy_url_pack = parse_url(http_session.proxies[http_scheme])
|
||||||
|
kw['http_proxy_host'] = proxy_url_pack.hostname
|
||||||
|
kw['http_proxy_port'] = proxy_url_pack.port
|
||||||
|
if proxy_url_pack.username:
|
||||||
|
kw['http_proxy_auth'] = (
|
||||||
|
proxy_url_pack.username, proxy_url_pack.password)
|
||||||
|
if http_session.verify:
|
||||||
|
if http_session.cert: # Specify certificate path on disk
|
||||||
|
if isinstance(http_session.cert, string_types):
|
||||||
|
kw['ca_certs'] = http_session.cert
|
||||||
|
else:
|
||||||
|
kw['ca_certs'] = http_session.cert[0]
|
||||||
|
else: # Do not verify the SSL certificate
|
||||||
|
kw['sslopt'] = {'cert_reqs': ssl.CERT_NONE}
|
||||||
|
try:
|
||||||
|
self._connection = websocket.create_connection(ws_url, **kw)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConnectionError(e)
|
||||||
|
|
||||||
|
def recv_packet(self):
|
||||||
|
try:
|
||||||
|
packet_text = self._connection.recv()
|
||||||
|
except websocket.WebSocketTimeoutException as e:
|
||||||
|
raise TimeoutError('recv timed out (%s)' % e)
|
||||||
|
except websocket.SSLError as e:
|
||||||
|
raise ConnectionError('recv disconnected by SSL (%s)' % e)
|
||||||
|
except websocket.WebSocketConnectionClosedException as e:
|
||||||
|
raise ConnectionError('recv disconnected (%s)' % e)
|
||||||
|
except socket.error as e:
|
||||||
|
raise ConnectionError('recv disconnected (%s)' % e)
|
||||||
|
engineIO_packet_type, engineIO_packet_data = parse_packet_text(
|
||||||
|
six.b(packet_text))
|
||||||
|
yield engineIO_packet_type, engineIO_packet_data
|
||||||
|
|
||||||
|
def send_packet(self, engineIO_packet_type, engineIO_packet_data=''):
|
||||||
|
packet = format_packet_text(engineIO_packet_type, engineIO_packet_data)
|
||||||
|
try:
|
||||||
|
self._connection.send(packet)
|
||||||
|
except websocket.WebSocketTimeoutException as e:
|
||||||
|
raise TimeoutError('send timed out (%s)' % e)
|
||||||
|
except socket.error as e:
|
||||||
|
raise ConnectionError('send disconnected (%s)' % e)
|
||||||
|
except websocket.WebSocketConnectionClosedException as e:
|
||||||
|
raise ConnectionError('send disconnected (%s)' % e)
|
||||||
|
|
||||||
|
def set_timeout(self, seconds=None):
|
||||||
|
self._connection.settimeout(seconds or self._timeout)
|
||||||
|
|
||||||
|
|
||||||
def get_response(request, *args, **kw):
|
def get_response(request, *args, **kw):
|
||||||
try:
|
try:
|
||||||
response = request(*args, **kw)
|
response = request(*args, stream=True, **kw)
|
||||||
except requests.exceptions.Timeout as e:
|
except requests.exceptions.Timeout as e:
|
||||||
raise TimeoutError(e)
|
raise TimeoutError(e)
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
|
@ -87,7 +182,8 @@ def get_response(request, *args, **kw):
|
||||||
raise ConnectionError('could not negotiate SSL (%s)' % e)
|
raise ConnectionError('could not negotiate SSL (%s)' % e)
|
||||||
status_code = response.status_code
|
status_code = response.status_code
|
||||||
if 200 != status_code:
|
if 200 != status_code:
|
||||||
raise ConnectionError('unexpected status code (%s)' % status_code)
|
raise ConnectionError('unexpected status code (%s %s)' % (
|
||||||
|
status_code, response.text))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -98,7 +194,7 @@ def prepare_http_session(kw):
|
||||||
http_session.proxies.update(kw.get('proxies', {}))
|
http_session.proxies.update(kw.get('proxies', {}))
|
||||||
http_session.hooks.update(kw.get('hooks', {}))
|
http_session.hooks.update(kw.get('hooks', {}))
|
||||||
http_session.params.update(kw.get('params', {}))
|
http_session.params.update(kw.get('params', {}))
|
||||||
http_session.verify = kw.get('verify')
|
http_session.verify = kw.get('verify', True)
|
||||||
http_session.cert = kw.get('cert')
|
http_session.cert = kw.get('cert')
|
||||||
http_session.cookies.update(kw.get('cookies', {}))
|
http_session.cookies.update(kw.get('cookies', {}))
|
||||||
return http_session
|
return http_session
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue