XSocket 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xsocket-2.1.0/LICENSE +24 -0
- xsocket-2.1.0/PKG-INFO +122 -0
- xsocket-2.1.0/README.md +108 -0
- xsocket-2.1.0/pyproject.toml +23 -0
- xsocket-2.1.0/setup.cfg +4 -0
- xsocket-2.1.0/xsocket/__init__.py +7 -0
- xsocket-2.1.0/xsocket/client.py +197 -0
- xsocket-2.1.0/xsocket/data.py +169 -0
- xsocket-2.1.0/xsocket/helpers.py +250 -0
- xsocket-2.1.0/xsocket/server.py +227 -0
- xsocket-2.1.0/xsocket/socket_object.py +255 -0
- xsocket-2.1.0/xsocket.egg-info/PKG-INFO +122 -0
- xsocket-2.1.0/xsocket.egg-info/SOURCES.txt +14 -0
- xsocket-2.1.0/xsocket.egg-info/dependency_links.txt +1 -0
- xsocket-2.1.0/xsocket.egg-info/requires.txt +1 -0
- xsocket-2.1.0/xsocket.egg-info/top_level.txt +1 -0
xsocket-2.1.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
[The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2009-2013 Jeff Mott
|
|
6
|
+
Copyright (c) 2013-2016 Evan Vosberg
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
|
16
|
+
all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
24
|
+
THE SOFTWARE.
|
xsocket-2.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xsocket
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: WebSocket client and server for Python (asyncio), compatible with xSocket for Node.js and the browser
|
|
5
|
+
Author: pr!de
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pride-oO/xsocket
|
|
8
|
+
Keywords: socket,ws,wss,websocket,message,client,server,asyncio
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: websockets>=13
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
rfr# xSocket for Python
|
|
16
|
+
|
|
17
|
+
WebSocket client and server for Python (asyncio). Same protocol as [xSocket](https://github.com/pride-oO/xsocket) for Node.js and the browser: a Python server works with JS clients, a Python client works with a Node.js server.
|
|
18
|
+
|
|
19
|
+
- Request-response with TTL (`sendReady`), clock synchronization with the server
|
|
20
|
+
- Automatic reconnect and session recovery within `socketObjectTimeout`
|
|
21
|
+
- Auth hook on the server (`use('auth')`)
|
|
22
|
+
- `ws://` or `wss://` depending on certificate, optional `perMessageDeflate` compression
|
|
23
|
+
- Binary messages: `bytes`, `bytearray` or `memoryview` as data, received as `bytes`
|
|
24
|
+
- Python 3.9+, depends on [websockets](https://pypi.org/project/websockets/)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install xsocket
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The API mirrors the JS version. Methods that return a `Promise` in JS (`send`, `sendReady`, `response`) return an awaitable task here: the message is sent even without `await`. Event handlers can be regular functions or coroutines and receive the same arguments as in JS. `Client` and `Server` must be created inside a running event loop.
|
|
31
|
+
|
|
32
|
+
## Server:
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
import xsocket
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
xServer = xsocket.Server({
|
|
39
|
+
'port' : 8752, # (Not necessary) Port listen (Default: 443 with certificate, 80 without)
|
|
40
|
+
'socketObjectTimeout' : 30000, # (Not necessary) Allows the user to reconnect within 30 seconds (Default: 0)
|
|
41
|
+
'perMessageDeflate' : True, # (Not necessary) Compress messages (Default: False)
|
|
42
|
+
'https' : { # (Not necessary) Without certificate server uses ws:// instead of wss://
|
|
43
|
+
'cert' : '<path>/cert.pem',
|
|
44
|
+
'key' : '<path>/private.key'
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
# (Not necessary) Auth, by default all connections are allowed. Can be async
|
|
49
|
+
def auth(xSocketObject, success, failed):
|
|
50
|
+
if xSocketObject.getQueryVal('name'):
|
|
51
|
+
return success()
|
|
52
|
+
failed('Access denied')
|
|
53
|
+
|
|
54
|
+
xServer.use('auth', auth)
|
|
55
|
+
|
|
56
|
+
def onConnect(xSocketObject):
|
|
57
|
+
print('Connect event:', xSocketObject.getID(), xSocketObject.getIP(), xSocketObject.getOrigin(), xSocketObject.getQuery(), xSocketObject.getReqHeaders())
|
|
58
|
+
|
|
59
|
+
async def onData(xSocketData):
|
|
60
|
+
print('Data event', xSocketData.getID(), xSocketData.getType(), xSocketData.getData())
|
|
61
|
+
await asyncio.sleep(1)
|
|
62
|
+
try:
|
|
63
|
+
await xSocketData.response({'welcome' : 'Hello ' + xSocketObject.getQueryVal('name')})
|
|
64
|
+
# OR
|
|
65
|
+
# await xSocketData.response({}, 'Permision denied')
|
|
66
|
+
except xsocket.XSocketError as e:
|
|
67
|
+
print('Response send failed', xSocketData.getID(), e)
|
|
68
|
+
|
|
69
|
+
xSocketObject.on('data', onData)
|
|
70
|
+
xSocketObject.on('disconnect', lambda xSocketObject, msg: print('Disconnect event:', xSocketObject.getID(), msg))
|
|
71
|
+
xSocketObject.on('reconnect', lambda xSocketObject: print('Reconnect event:', xSocketObject.getID()))
|
|
72
|
+
xSocketObject.on('destroy', lambda xSocketObject, msg: print('Destroy event:', xSocketObject.getID(), msg))
|
|
73
|
+
|
|
74
|
+
xServer.on('connect', onConnect)
|
|
75
|
+
xServer.on('listen', lambda: print('Listen event', xServer.getPort()))
|
|
76
|
+
|
|
77
|
+
await xServer.listen()
|
|
78
|
+
await asyncio.Future()
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Client:
|
|
84
|
+
```python
|
|
85
|
+
import asyncio
|
|
86
|
+
import xsocket
|
|
87
|
+
|
|
88
|
+
async def main():
|
|
89
|
+
xClient = xsocket.Client(['wss://server_domain.com:8752'], {'name' : 'Bob'}) # ws:// if server without certificate
|
|
90
|
+
# Self-signed certificate: xsocket.Client(url, query, {'ssl' : ssl.SSLContext})
|
|
91
|
+
|
|
92
|
+
xClient.on('reconnect', lambda xSocketObject: print('reconnect event', xSocketObject.getID()))
|
|
93
|
+
xClient.on('disconnect', lambda xSocketObject, msg: print('disconnect', xSocketObject.getID(), msg))
|
|
94
|
+
xClient.on('destroy', lambda xSocketObject, msg: print('destroy', xSocketObject.getID(), msg))
|
|
95
|
+
|
|
96
|
+
# Messages from server
|
|
97
|
+
xClient.on('data', lambda xSocketData: xSocketData.response({'ok' : True}))
|
|
98
|
+
|
|
99
|
+
xSocketObject = await xClient.getSocketObjectReady()
|
|
100
|
+
print('connect event:', xSocketObject.getID())
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
xSocketData = await xClient.sendReady(
|
|
104
|
+
'helloServer', # Name message
|
|
105
|
+
{'message' : 'Hello server'}, # Data message
|
|
106
|
+
2000 # TTL ready response (Default: 3s)
|
|
107
|
+
)
|
|
108
|
+
print('Server responsed #' + xSocketData.getID(), xSocketData.getType(), xSocketData.getResData())
|
|
109
|
+
except xsocket.XSocketError as e:
|
|
110
|
+
print('Server responsed error', e)
|
|
111
|
+
|
|
112
|
+
# Binary message, response can be binary too
|
|
113
|
+
# xSocketData = await xClient.sendReady('file', b'\x01\x02\x03')
|
|
114
|
+
|
|
115
|
+
# Without waiting for response
|
|
116
|
+
# await xClient.send('log', {'message' : 'Hello'}, 0)
|
|
117
|
+
|
|
118
|
+
# Close connection and destroy session on server
|
|
119
|
+
await xClient.close()
|
|
120
|
+
|
|
121
|
+
asyncio.run(main())
|
|
122
|
+
```
|
xsocket-2.1.0/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
rfr# xSocket for Python
|
|
2
|
+
|
|
3
|
+
WebSocket client and server for Python (asyncio). Same protocol as [xSocket](https://github.com/pride-oO/xsocket) for Node.js and the browser: a Python server works with JS clients, a Python client works with a Node.js server.
|
|
4
|
+
|
|
5
|
+
- Request-response with TTL (`sendReady`), clock synchronization with the server
|
|
6
|
+
- Automatic reconnect and session recovery within `socketObjectTimeout`
|
|
7
|
+
- Auth hook on the server (`use('auth')`)
|
|
8
|
+
- `ws://` or `wss://` depending on certificate, optional `perMessageDeflate` compression
|
|
9
|
+
- Binary messages: `bytes`, `bytearray` or `memoryview` as data, received as `bytes`
|
|
10
|
+
- Python 3.9+, depends on [websockets](https://pypi.org/project/websockets/)
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pip install xsocket
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The API mirrors the JS version. Methods that return a `Promise` in JS (`send`, `sendReady`, `response`) return an awaitable task here: the message is sent even without `await`. Event handlers can be regular functions or coroutines and receive the same arguments as in JS. `Client` and `Server` must be created inside a running event loop.
|
|
17
|
+
|
|
18
|
+
## Server:
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
import xsocket
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
xServer = xsocket.Server({
|
|
25
|
+
'port' : 8752, # (Not necessary) Port listen (Default: 443 with certificate, 80 without)
|
|
26
|
+
'socketObjectTimeout' : 30000, # (Not necessary) Allows the user to reconnect within 30 seconds (Default: 0)
|
|
27
|
+
'perMessageDeflate' : True, # (Not necessary) Compress messages (Default: False)
|
|
28
|
+
'https' : { # (Not necessary) Without certificate server uses ws:// instead of wss://
|
|
29
|
+
'cert' : '<path>/cert.pem',
|
|
30
|
+
'key' : '<path>/private.key'
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
# (Not necessary) Auth, by default all connections are allowed. Can be async
|
|
35
|
+
def auth(xSocketObject, success, failed):
|
|
36
|
+
if xSocketObject.getQueryVal('name'):
|
|
37
|
+
return success()
|
|
38
|
+
failed('Access denied')
|
|
39
|
+
|
|
40
|
+
xServer.use('auth', auth)
|
|
41
|
+
|
|
42
|
+
def onConnect(xSocketObject):
|
|
43
|
+
print('Connect event:', xSocketObject.getID(), xSocketObject.getIP(), xSocketObject.getOrigin(), xSocketObject.getQuery(), xSocketObject.getReqHeaders())
|
|
44
|
+
|
|
45
|
+
async def onData(xSocketData):
|
|
46
|
+
print('Data event', xSocketData.getID(), xSocketData.getType(), xSocketData.getData())
|
|
47
|
+
await asyncio.sleep(1)
|
|
48
|
+
try:
|
|
49
|
+
await xSocketData.response({'welcome' : 'Hello ' + xSocketObject.getQueryVal('name')})
|
|
50
|
+
# OR
|
|
51
|
+
# await xSocketData.response({}, 'Permision denied')
|
|
52
|
+
except xsocket.XSocketError as e:
|
|
53
|
+
print('Response send failed', xSocketData.getID(), e)
|
|
54
|
+
|
|
55
|
+
xSocketObject.on('data', onData)
|
|
56
|
+
xSocketObject.on('disconnect', lambda xSocketObject, msg: print('Disconnect event:', xSocketObject.getID(), msg))
|
|
57
|
+
xSocketObject.on('reconnect', lambda xSocketObject: print('Reconnect event:', xSocketObject.getID()))
|
|
58
|
+
xSocketObject.on('destroy', lambda xSocketObject, msg: print('Destroy event:', xSocketObject.getID(), msg))
|
|
59
|
+
|
|
60
|
+
xServer.on('connect', onConnect)
|
|
61
|
+
xServer.on('listen', lambda: print('Listen event', xServer.getPort()))
|
|
62
|
+
|
|
63
|
+
await xServer.listen()
|
|
64
|
+
await asyncio.Future()
|
|
65
|
+
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Client:
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
import xsocket
|
|
73
|
+
|
|
74
|
+
async def main():
|
|
75
|
+
xClient = xsocket.Client(['wss://server_domain.com:8752'], {'name' : 'Bob'}) # ws:// if server without certificate
|
|
76
|
+
# Self-signed certificate: xsocket.Client(url, query, {'ssl' : ssl.SSLContext})
|
|
77
|
+
|
|
78
|
+
xClient.on('reconnect', lambda xSocketObject: print('reconnect event', xSocketObject.getID()))
|
|
79
|
+
xClient.on('disconnect', lambda xSocketObject, msg: print('disconnect', xSocketObject.getID(), msg))
|
|
80
|
+
xClient.on('destroy', lambda xSocketObject, msg: print('destroy', xSocketObject.getID(), msg))
|
|
81
|
+
|
|
82
|
+
# Messages from server
|
|
83
|
+
xClient.on('data', lambda xSocketData: xSocketData.response({'ok' : True}))
|
|
84
|
+
|
|
85
|
+
xSocketObject = await xClient.getSocketObjectReady()
|
|
86
|
+
print('connect event:', xSocketObject.getID())
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
xSocketData = await xClient.sendReady(
|
|
90
|
+
'helloServer', # Name message
|
|
91
|
+
{'message' : 'Hello server'}, # Data message
|
|
92
|
+
2000 # TTL ready response (Default: 3s)
|
|
93
|
+
)
|
|
94
|
+
print('Server responsed #' + xSocketData.getID(), xSocketData.getType(), xSocketData.getResData())
|
|
95
|
+
except xsocket.XSocketError as e:
|
|
96
|
+
print('Server responsed error', e)
|
|
97
|
+
|
|
98
|
+
# Binary message, response can be binary too
|
|
99
|
+
# xSocketData = await xClient.sendReady('file', b'\x01\x02\x03')
|
|
100
|
+
|
|
101
|
+
# Without waiting for response
|
|
102
|
+
# await xClient.send('log', {'message' : 'Hello'}, 0)
|
|
103
|
+
|
|
104
|
+
# Close connection and destroy session on server
|
|
105
|
+
await xClient.close()
|
|
106
|
+
|
|
107
|
+
asyncio.run(main())
|
|
108
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xsocket"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "WebSocket client and server for Python (asyncio), compatible with xSocket for Node.js and the browser"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "pr!de" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["websockets>=13"]
|
|
14
|
+
keywords = ["socket", "ws", "wss", "websocket", "message", "client", "server", "asyncio"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/pride-oO/xsocket"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools]
|
|
20
|
+
packages = ["xsocket"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.dynamic]
|
|
23
|
+
version = { attr = "xsocket.helpers.VERSION" }
|
xsocket-2.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from urllib.parse import quote, urlencode
|
|
3
|
+
|
|
4
|
+
from websockets.asyncio.client import connect
|
|
5
|
+
from websockets.exceptions import ConnectionClosed
|
|
6
|
+
|
|
7
|
+
from . import helpers
|
|
8
|
+
from .helpers import Connection, EventEmitter, XSocketError, logger
|
|
9
|
+
from .socket_object import SocketObject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Client(EventEmitter):
|
|
13
|
+
|
|
14
|
+
def __init__(self, serverUrl, query=None, settings=None):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.setMaxListeners(0)
|
|
17
|
+
|
|
18
|
+
if isinstance(serverUrl, str):
|
|
19
|
+
self._urlList = [serverUrl]
|
|
20
|
+
elif isinstance(serverUrl, (list, tuple)):
|
|
21
|
+
self._urlList = list(serverUrl)
|
|
22
|
+
else:
|
|
23
|
+
self._urlList = []
|
|
24
|
+
if not self._urlList:
|
|
25
|
+
raise XSocketError('Invalid urlList')
|
|
26
|
+
self._query = dict(query) if isinstance(query, dict) else {}
|
|
27
|
+
self._settings = dict(settings) if isinstance(settings, dict) else {}
|
|
28
|
+
self._ws = None
|
|
29
|
+
self._socketObject = None
|
|
30
|
+
self._isVersionWarned = False
|
|
31
|
+
self._isClose = False
|
|
32
|
+
|
|
33
|
+
self.on('connect', self.__onConnect)
|
|
34
|
+
self._runTask = helpers.promise(self.__run())
|
|
35
|
+
|
|
36
|
+
def getSettings(self):
|
|
37
|
+
return self._settings
|
|
38
|
+
|
|
39
|
+
def getSettingsVal(self, key, default=None):
|
|
40
|
+
return self.getSettings().get(key) or default
|
|
41
|
+
|
|
42
|
+
def getSocketObject(self):
|
|
43
|
+
return self._socketObject or False
|
|
44
|
+
|
|
45
|
+
async def getSocketObjectReady(self):
|
|
46
|
+
if self.getSocketObject():
|
|
47
|
+
return self.getSocketObject()
|
|
48
|
+
future = asyncio.get_running_loop().create_future()
|
|
49
|
+
self.once('connect', lambda socketObject: future.done() or future.set_result(socketObject))
|
|
50
|
+
return await future
|
|
51
|
+
|
|
52
|
+
def send(self, type, data=None, ttl=None):
|
|
53
|
+
return helpers.promise(self.__send(type, data, ttl))
|
|
54
|
+
|
|
55
|
+
async def __send(self, type, data, ttl):
|
|
56
|
+
socketObject = await self.getSocketObjectReady()
|
|
57
|
+
return await socketObject.send(type, data, ttl)
|
|
58
|
+
|
|
59
|
+
def sendReady(self, type, data=None, ttl=None):
|
|
60
|
+
return helpers.promise(self.__sendReady(type, data, ttl))
|
|
61
|
+
|
|
62
|
+
sendReadyResponse = sendReady
|
|
63
|
+
|
|
64
|
+
async def __sendReady(self, type, data, ttl):
|
|
65
|
+
socketObject = await self.getSocketObjectReady()
|
|
66
|
+
return await socketObject.sendReady(type, data, ttl)
|
|
67
|
+
|
|
68
|
+
def disconnect(self, msg=None):
|
|
69
|
+
socketObject = self.getSocketObject()
|
|
70
|
+
if not socketObject:
|
|
71
|
+
return False
|
|
72
|
+
return socketObject.disconnect(msg)
|
|
73
|
+
|
|
74
|
+
async def close(self, msg='end'):
|
|
75
|
+
self._isClose = True
|
|
76
|
+
if not self._ws or not self._ws.closeConnection(msg):
|
|
77
|
+
self._runTask.cancel()
|
|
78
|
+
await asyncio.gather(self._runTask, return_exceptions=True)
|
|
79
|
+
if self._socketObject:
|
|
80
|
+
self._socketObject.destroy(msg)
|
|
81
|
+
|
|
82
|
+
def __getUrl(self):
|
|
83
|
+
url = self._urlList[helpers.getRandInt(0, len(self._urlList) - 1)]
|
|
84
|
+
query = dict(self._query)
|
|
85
|
+
query['xV'] = helpers.VERSION
|
|
86
|
+
if self._socketObject and self._socketObject.getID() and self._socketObject.getSign():
|
|
87
|
+
query['xSOId'] = self._socketObject.getID()
|
|
88
|
+
query['xSOSign'] = self._socketObject.getSign()
|
|
89
|
+
return url + ('&' if '?' in url else '?') + urlencode({str(key): str(val) for key, val in query.items()}, quote_via=quote)
|
|
90
|
+
|
|
91
|
+
async def __run(self):
|
|
92
|
+
while not self._isClose:
|
|
93
|
+
try:
|
|
94
|
+
websocket = await connect(self.__getUrl(), ssl=self.getSettingsVal('ssl'), max_size=helpers.MAX_MESSAGE_SIZE)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.emit('ws|error', str(e))
|
|
97
|
+
await asyncio.sleep(1)
|
|
98
|
+
continue
|
|
99
|
+
await self.__handleConnection(websocket)
|
|
100
|
+
if not self._isClose:
|
|
101
|
+
await asyncio.sleep(1)
|
|
102
|
+
|
|
103
|
+
async def __handleConnection(self, websocket):
|
|
104
|
+
ws = Connection(websocket)
|
|
105
|
+
self._ws = ws
|
|
106
|
+
if self._isClose:
|
|
107
|
+
ws.closeConnection('end')
|
|
108
|
+
self.emit('ws|connect', ws)
|
|
109
|
+
msgInc = 0
|
|
110
|
+
|
|
111
|
+
async def pongCheck():
|
|
112
|
+
nonlocal msgInc
|
|
113
|
+
while True:
|
|
114
|
+
await asyncio.sleep(21)
|
|
115
|
+
if not msgInc:
|
|
116
|
+
ws.closeConnection('pingTimeout')
|
|
117
|
+
msgInc = 0
|
|
118
|
+
|
|
119
|
+
pongTask = helpers.promise(pongCheck())
|
|
120
|
+
try:
|
|
121
|
+
async for message in websocket:
|
|
122
|
+
msgInc += 1
|
|
123
|
+
if message == 'ping':
|
|
124
|
+
ws.sendMessage('pong')
|
|
125
|
+
continue
|
|
126
|
+
try:
|
|
127
|
+
self.__onMessage(ws, message)
|
|
128
|
+
except Exception:
|
|
129
|
+
logger.exception('Message handler error')
|
|
130
|
+
except ConnectionClosed:
|
|
131
|
+
pass
|
|
132
|
+
finally:
|
|
133
|
+
pongTask.cancel()
|
|
134
|
+
await websocket.wait_closed()
|
|
135
|
+
ws.stop()
|
|
136
|
+
self._ws = None
|
|
137
|
+
msg = websocket.close_reason or 'close'
|
|
138
|
+
self.emit('ws|disconnect', msg)
|
|
139
|
+
if self._socketObject:
|
|
140
|
+
self._socketObject.emit('ws|disconnect', msg)
|
|
141
|
+
|
|
142
|
+
def __onMessage(self, ws, message):
|
|
143
|
+
body = helpers.decodeMessage(message)
|
|
144
|
+
if not body:
|
|
145
|
+
return
|
|
146
|
+
if body[0] != helpers.MSG_SO_UPDATE:
|
|
147
|
+
if self._socketObject:
|
|
148
|
+
self._socketObject.emit('ws|message', body)
|
|
149
|
+
return
|
|
150
|
+
body = body + [None] * (6 - len(body))
|
|
151
|
+
if body[4] != helpers.VERSION and not self._isVersionWarned:
|
|
152
|
+
self._isVersionWarned = True
|
|
153
|
+
logger.warning('Version mismatch client %s, server %s', helpers.VERSION, body[4] or 'unknown')
|
|
154
|
+
ws.setTimeOffset(body[5] - helpers.getTime() if helpers.isNumber(body[5]) else 0)
|
|
155
|
+
ws.setQuery({
|
|
156
|
+
'xSOId' : str(body[1]),
|
|
157
|
+
'xSOSign' : str(body[2]),
|
|
158
|
+
'configure' : body[3] if isinstance(body[3], dict) else {}
|
|
159
|
+
})
|
|
160
|
+
if self._socketObject:
|
|
161
|
+
query = ws.getQuery()
|
|
162
|
+
if query['xSOId'] == self._socketObject.getID() and query['xSOSign'] == self._socketObject.getSign() and self._socketObject.update(ws):
|
|
163
|
+
return
|
|
164
|
+
self._socketObject.destroy('update')
|
|
165
|
+
self._socketObject = None
|
|
166
|
+
socketObject = self._socketObject = SocketObject()
|
|
167
|
+
socketObject.once('connect', lambda socketObject: self.emit('connect', socketObject))
|
|
168
|
+
socketObject.on('destroy', self.__onSocketObjectDestroy)
|
|
169
|
+
socketObject.on('disconnect', lambda socketObject, msg: self.emit('disconnect', socketObject, msg))
|
|
170
|
+
socketObject.on('reconnect', lambda socketObject: self.emit('reconnect', socketObject))
|
|
171
|
+
socketObject.on('data', lambda socketData: self.emit('data', socketData))
|
|
172
|
+
if not socketObject.update(ws):
|
|
173
|
+
ws.closeConnection('update')
|
|
174
|
+
|
|
175
|
+
def __onSocketObjectDestroy(self, socketObject, msg):
|
|
176
|
+
if self._socketObject is socketObject:
|
|
177
|
+
self._socketObject = None
|
|
178
|
+
self.emit('destroy', socketObject, msg)
|
|
179
|
+
|
|
180
|
+
def __onConnect(self, socketObject):
|
|
181
|
+
loop = asyncio.get_running_loop()
|
|
182
|
+
timerMap = {'destroy' : None}
|
|
183
|
+
|
|
184
|
+
def setDestroyTimeout(action, ttl=0):
|
|
185
|
+
if timerMap['destroy']:
|
|
186
|
+
timerMap['destroy'].cancel()
|
|
187
|
+
timerMap['destroy'] = None
|
|
188
|
+
if action:
|
|
189
|
+
timerMap['destroy'] = loop.call_later(max(ttl, 0) / 1000, socketObject.destroy, 'socketObjectTimeout')
|
|
190
|
+
|
|
191
|
+
def disconnectHandler(socketObject, msg):
|
|
192
|
+
configure = socketObject.getQueryVal('configure', {})
|
|
193
|
+
setDestroyTimeout(True, helpers.toInt(configure.get('socketObjectTimeout') if isinstance(configure, dict) else 0))
|
|
194
|
+
|
|
195
|
+
socketObject.on('reconnect', lambda socketObject: setDestroyTimeout(False))
|
|
196
|
+
socketObject.on('disconnect', disconnectHandler)
|
|
197
|
+
socketObject.once('destroy', lambda socketObject, msg: setDestroyTimeout(False))
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from . import helpers
|
|
4
|
+
from .helpers import EventEmitter, XSocketError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Data(EventEmitter):
|
|
8
|
+
|
|
9
|
+
STATUS_NEW = 0
|
|
10
|
+
STATUS_SEND = 1
|
|
11
|
+
STATUS_RESPONSE = 2
|
|
12
|
+
DEFAULT_TTL = 3000
|
|
13
|
+
|
|
14
|
+
def __init__(self, isOutput, dataObject=None, socketObject=None):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.setMaxListeners(0)
|
|
17
|
+
dataObject = dataObject if isinstance(dataObject, dict) else {}
|
|
18
|
+
|
|
19
|
+
ID = dataObject.get('ID')
|
|
20
|
+
type = dataObject.get('type')
|
|
21
|
+
data = dataObject.get('data')
|
|
22
|
+
ttl = dataObject.get('ttl')
|
|
23
|
+
time = dataObject.get('time')
|
|
24
|
+
|
|
25
|
+
self._isOutput = bool(isOutput)
|
|
26
|
+
self._socketObject = socketObject
|
|
27
|
+
self._ID = ID if isinstance(ID, str) else ''
|
|
28
|
+
self._type = type if isinstance(type, str) else ''
|
|
29
|
+
self._data = data if helpers.isObject(data) else {}
|
|
30
|
+
self._ttl = ttl if helpers.isInt(ttl) and ttl >= 0 else Data.DEFAULT_TTL
|
|
31
|
+
now = socketObject.getTime() if socketObject else helpers.getTime()
|
|
32
|
+
self._time = time if helpers.isNumber(time) and time > 0 else now
|
|
33
|
+
self._resData = {}
|
|
34
|
+
self._error = False
|
|
35
|
+
self._status = Data.STATUS_NEW
|
|
36
|
+
self._destroy = False
|
|
37
|
+
self._ttlTimeout = asyncio.get_running_loop().call_later(
|
|
38
|
+
max(0, min(self._ttl, self._ttl - (now - self._time))) / 1000,
|
|
39
|
+
self.destroy, 'ttl', True
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def getID(self):
|
|
43
|
+
return str(self._ID)
|
|
44
|
+
|
|
45
|
+
def getType(self):
|
|
46
|
+
return str(self._type)
|
|
47
|
+
|
|
48
|
+
def getTTL(self):
|
|
49
|
+
return self._ttl
|
|
50
|
+
|
|
51
|
+
def getTime(self):
|
|
52
|
+
return self._time
|
|
53
|
+
|
|
54
|
+
def getData(self):
|
|
55
|
+
return self._data
|
|
56
|
+
|
|
57
|
+
def getDataVal(self, key, default=None):
|
|
58
|
+
try:
|
|
59
|
+
return self.getData()[key] or default
|
|
60
|
+
except (KeyError, IndexError, TypeError):
|
|
61
|
+
return default
|
|
62
|
+
|
|
63
|
+
def getResData(self):
|
|
64
|
+
return self._resData
|
|
65
|
+
|
|
66
|
+
def getResDataVal(self, key, default=None):
|
|
67
|
+
try:
|
|
68
|
+
return self.getResData()[key] or default
|
|
69
|
+
except (KeyError, IndexError, TypeError):
|
|
70
|
+
return default
|
|
71
|
+
|
|
72
|
+
def getStatus(self):
|
|
73
|
+
return self._status
|
|
74
|
+
|
|
75
|
+
def getDestroy(self):
|
|
76
|
+
return self._destroy or False
|
|
77
|
+
|
|
78
|
+
def getSocketObject(self):
|
|
79
|
+
return self._socketObject
|
|
80
|
+
|
|
81
|
+
def getError(self):
|
|
82
|
+
return self._error or False
|
|
83
|
+
|
|
84
|
+
def isNew(self):
|
|
85
|
+
return self._status == Data.STATUS_NEW
|
|
86
|
+
|
|
87
|
+
def isSend(self):
|
|
88
|
+
return self._status == Data.STATUS_SEND
|
|
89
|
+
|
|
90
|
+
def isResponse(self):
|
|
91
|
+
return self._status == Data.STATUS_RESPONSE
|
|
92
|
+
|
|
93
|
+
def response(self, data=None, error=None):
|
|
94
|
+
return helpers.promise(self.__response(data, error))
|
|
95
|
+
|
|
96
|
+
async def __response(self, data, error):
|
|
97
|
+
if self._isOutput:
|
|
98
|
+
raise XSocketError('Output data')
|
|
99
|
+
if self._destroy or self.isResponse() or not self._ttl:
|
|
100
|
+
raise XSocketError('destroy')
|
|
101
|
+
self.setResData(data)
|
|
102
|
+
self.setError(error)
|
|
103
|
+
future = asyncio.get_running_loop().create_future()
|
|
104
|
+
|
|
105
|
+
def responseHandler(resData, error):
|
|
106
|
+
if not future.done():
|
|
107
|
+
future.set_result(self)
|
|
108
|
+
|
|
109
|
+
def destroyHandler(msg):
|
|
110
|
+
if not future.done():
|
|
111
|
+
future.set_exception(XSocketError(msg))
|
|
112
|
+
|
|
113
|
+
self.once('response', responseHandler)
|
|
114
|
+
self.once('destroy', destroyHandler)
|
|
115
|
+
try:
|
|
116
|
+
ws = await self._socketObject.getWsReady()
|
|
117
|
+
if ws.sendObject([
|
|
118
|
+
helpers.MSG_SD_UPDATE,
|
|
119
|
+
int(self._ID),
|
|
120
|
+
self.getResData(),
|
|
121
|
+
self.getError()
|
|
122
|
+
]):
|
|
123
|
+
self.setStatus(Data.STATUS_RESPONSE)
|
|
124
|
+
else:
|
|
125
|
+
self.destroy('Send failed')
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.destroy(str(e) or 'Send failed')
|
|
128
|
+
return await future
|
|
129
|
+
|
|
130
|
+
def setStatus(self, status):
|
|
131
|
+
if (
|
|
132
|
+
status not in (Data.STATUS_SEND, Data.STATUS_RESPONSE) or
|
|
133
|
+
self._status == Data.STATUS_RESPONSE or
|
|
134
|
+
self._status == status
|
|
135
|
+
):
|
|
136
|
+
return False
|
|
137
|
+
self._status = status
|
|
138
|
+
self.emit('setStatus', status)
|
|
139
|
+
if status == Data.STATUS_RESPONSE:
|
|
140
|
+
self.emit('response', self.getResData(), self.getError())
|
|
141
|
+
self.destroy('response')
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
def setError(self, errorMsg):
|
|
145
|
+
if not isinstance(errorMsg, str):
|
|
146
|
+
return False
|
|
147
|
+
self._error = errorMsg
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
def setResData(self, data):
|
|
151
|
+
if data is None:
|
|
152
|
+
data = {}
|
|
153
|
+
if not helpers.isObject(data):
|
|
154
|
+
return False
|
|
155
|
+
self._resData = data
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
def destroy(self, msg=None, isSetError=False):
|
|
159
|
+
if self._destroy:
|
|
160
|
+
return False
|
|
161
|
+
self._destroy = msg if isinstance(msg, str) and msg else 'destroy'
|
|
162
|
+
self._ttlTimeout.cancel()
|
|
163
|
+
if isSetError and not self._error:
|
|
164
|
+
self.setError(self._destroy)
|
|
165
|
+
if self._status != Data.STATUS_RESPONSE:
|
|
166
|
+
self.setStatus(Data.STATUS_RESPONSE)
|
|
167
|
+
self.emit('destroy', self._destroy)
|
|
168
|
+
self.clearAllListeners()
|
|
169
|
+
return True
|