vortexpy 4.0.23__py3-none-any.whl
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.
- vortex/DataWrapTuple.py +27 -0
- vortex/DeferUtil.py +224 -0
- vortex/Jsonable.py +304 -0
- vortex/Payload.py +115 -0
- vortex/PayloadEndpoint.py +216 -0
- vortex/PayloadEndpointTest.py +164 -0
- vortex/PayloadEnvelope.py +166 -0
- vortex/PayloadFilterKeys.py +15 -0
- vortex/PayloadIO.py +152 -0
- vortex/PayloadPriority.py +11 -0
- vortex/PayloadResponse.py +173 -0
- vortex/PayloadResponseTest.py +39 -0
- vortex/PayloadTest.py +136 -0
- vortex/PerformTestActionTuple.py +30 -0
- vortex/SerialiseUtil.py +273 -0
- vortex/TestTuple.py +61 -0
- vortex/Tuple.py +968 -0
- vortex/TupleAction.py +72 -0
- vortex/TupleActionVortex.py +5 -0
- vortex/TupleDataForTest.py +25 -0
- vortex/TuplePerfTest.py +71 -0
- vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
- vortex/TupleRestfulTest.py +140 -0
- vortex/TupleSelector.py +105 -0
- vortex/TupleTest.py +127 -0
- vortex/VortexABC.py +55 -0
- vortex/VortexClientHttp.py +308 -0
- vortex/VortexClientHttpTest.py +71 -0
- vortex/VortexClientTcp.py +355 -0
- vortex/VortexClientWebsocketFactory.py +383 -0
- vortex/VortexConnectionABC.py +88 -0
- vortex/VortexFactory.py +932 -0
- vortex/VortexPayloadProtocol.py +180 -0
- vortex/VortexServer.py +367 -0
- vortex/VortexServerConnection.py +127 -0
- vortex/VortexServerHttpResource.py +193 -0
- vortex/VortexServerTcp.py +104 -0
- vortex/VortexServerWebsocket.py +287 -0
- vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
- vortex/VortexTcpMemoryLeakRawTest.py +87 -0
- vortex/VortexTest.py +22 -0
- vortex/VortexUtil.py +314 -0
- vortex/VortexWritePushProducer.py +321 -0
- vortex/__init__.py +10 -0
- vortex/data_loader/TupleDataLiveLockManager.py +383 -0
- vortex/data_loader/TupleDataLoader.py +326 -0
- vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
- vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
- vortex/data_loader/TupleDataLoaderTuples.py +53 -0
- vortex/data_loader/__init__.py +0 -0
- vortex/event_bus/TupleChangeEventABC.py +15 -0
- vortex/event_bus/TupleChangeEventBus.py +192 -0
- vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
- vortex/event_bus/__init__.py +0 -0
- vortex/handler/TupleActionProcessor.py +270 -0
- vortex/handler/TupleActionProcessorProxy.py +218 -0
- vortex/handler/TupleActionProcessorTestHandler.py +27 -0
- vortex/handler/TupleDataActionClient.py +67 -0
- vortex/handler/TupleDataObservableCache.py +201 -0
- vortex/handler/TupleDataObservableHandler.py +371 -0
- vortex/handler/TupleDataObservableProxyHandler.py +340 -0
- vortex/handler/TupleDataObservableTestHandler.py +60 -0
- vortex/handler/TupleDataObserverClient.py +158 -0
- vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
- vortex/handler/__init__.py +0 -0
- vortex/restful/GzippedDataHttpClient.py +170 -0
- vortex/restful/GzippedDataHttpClientTest.py +103 -0
- vortex/restful/GzippedPayloadHttpClient.py +181 -0
- vortex/restful/RestfulHttpClient.py +107 -0
- vortex/restful/RestfulHttpClientTest.py +60 -0
- vortex/restful/RestfulResource.py +153 -0
- vortex/restful/RestfulResourceTest.py +100 -0
- vortex/restful/__init__.py +0 -0
- vortex/rpc/RPC.py +446 -0
- vortex/rpc/RPCTest.py +12 -0
- vortex/rpc/__init__.py +0 -0
- vortex/sqla_orm/OrmCrudHandler.py +431 -0
- vortex/sqla_orm/TupleProviderForDb.py +42 -0
- vortex/sqla_orm/VortexTupleJson.py +24 -0
- vortex/sqla_orm/__init__.py +0 -0
- vortex/storage/TupleStorageSqlite.py +359 -0
- vortex/storage/__init__.py +0 -0
- vortexpy-4.0.23.dist-info/LICENSE +21 -0
- vortexpy-4.0.23.dist-info/METADATA +43 -0
- vortexpy-4.0.23.dist-info/RECORD +87 -0
- vortexpy-4.0.23.dist-info/WHEEL +5 -0
- vortexpy-4.0.23.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* Created by Synerty Pty Ltd
|
|
3
|
+
*
|
|
4
|
+
* This software is open source, the MIT license applies.
|
|
5
|
+
*
|
|
6
|
+
* Website : http://www.synerty.com
|
|
7
|
+
* Support : support@synerty.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABCMeta, abstractmethod
|
|
12
|
+
from collections import deque
|
|
13
|
+
|
|
14
|
+
from twisted.internet import reactor
|
|
15
|
+
from twisted.internet.defer import inlineCallbacks
|
|
16
|
+
from twisted.internet.error import ConnectionDone, ConnectionLost
|
|
17
|
+
from twisted.internet.protocol import Protocol, connectionDone
|
|
18
|
+
|
|
19
|
+
from vortex.DeferUtil import nonConcurrentMethod
|
|
20
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
21
|
+
from vortex.PayloadIO import PayloadIO
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(name=__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VortexPayloadProtocol(Protocol, metaclass=ABCMeta):
|
|
27
|
+
def __init__(self, logger):
|
|
28
|
+
self._serverVortexUuid = None
|
|
29
|
+
self._serverVortexName = None
|
|
30
|
+
self._logger = logger
|
|
31
|
+
|
|
32
|
+
self._data = b""
|
|
33
|
+
self._vortexMsgsQueue = deque()
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def _beat(self):
|
|
37
|
+
"""
|
|
38
|
+
EG :
|
|
39
|
+
if self._vortexClient:
|
|
40
|
+
self._vortexClient._beat()
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def _nameAndUuidReceived(self, name, uuid):
|
|
45
|
+
"""
|
|
46
|
+
EG :
|
|
47
|
+
if self._vortexClient:
|
|
48
|
+
self._vortexClient._setNameAndUuid(name=self._serverVortexName,
|
|
49
|
+
uuid=self._serverVortexUuid)
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def _createResponseSenderCallable(self):
|
|
54
|
+
"""
|
|
55
|
+
EG :
|
|
56
|
+
def sendResponse(vortexMsgs: Union[VortexMsgList, bytes]):
|
|
57
|
+
return self._vortexClient.sendVortexMsg(vortexMsgs=vortexMsgs)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def vortexMsgReceived(self, vortexMsg):
|
|
61
|
+
"""Vortex Msg Received
|
|
62
|
+
|
|
63
|
+
For protocols where framing is used, we don't need to process messages
|
|
64
|
+
with dots on the end, for those protocols, call this method to process
|
|
65
|
+
the whole message.
|
|
66
|
+
"""
|
|
67
|
+
self._data = b""
|
|
68
|
+
self._beat()
|
|
69
|
+
if vortexMsg == b".":
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
self._vortexMsgsQueue.append(vortexMsg)
|
|
73
|
+
|
|
74
|
+
if self._vortexMsgsQueue and not self._processVortexMsgs.running:
|
|
75
|
+
reactor.callLater(0, self._processVortexMsgs)
|
|
76
|
+
|
|
77
|
+
def dataReceived(self, bytesIn):
|
|
78
|
+
"""Data Received
|
|
79
|
+
|
|
80
|
+
This method is used for protocols that don't have framing. We use a
|
|
81
|
+
'.' to delineate messages.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
if bytesIn.startswith(b"<"):
|
|
85
|
+
raise Exception("Not Logged In")
|
|
86
|
+
|
|
87
|
+
self._data += bytesIn
|
|
88
|
+
self._beat()
|
|
89
|
+
self._processData()
|
|
90
|
+
|
|
91
|
+
def connectionLost(self, reason=connectionDone):
|
|
92
|
+
reasonFailure = reason
|
|
93
|
+
self._processData()
|
|
94
|
+
if reasonFailure.check(ConnectionDone):
|
|
95
|
+
self._logger.info(
|
|
96
|
+
"Connection closed by other end (it may be shutting down)"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
elif isinstance(reasonFailure.value, ConnectionLost):
|
|
100
|
+
self._logger.info(
|
|
101
|
+
"Connection to other end lost (We may be shutting down)"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
self._logger.error("Closed with error")
|
|
106
|
+
try:
|
|
107
|
+
self._logger.exception(reason.getErrorMessage())
|
|
108
|
+
self._logger.exception(reason.getTraceback())
|
|
109
|
+
except:
|
|
110
|
+
self._logger.exception(reasonFailure.value)
|
|
111
|
+
|
|
112
|
+
def _processData(self):
|
|
113
|
+
def getNextChunkIter():
|
|
114
|
+
try:
|
|
115
|
+
while True:
|
|
116
|
+
yield self._data.index(b".")
|
|
117
|
+
except ValueError:
|
|
118
|
+
# There is no '.' in it, wait for more data.
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
if self._data:
|
|
122
|
+
for nextChunk in getNextChunkIter():
|
|
123
|
+
vortexMsg = self._data[:nextChunk]
|
|
124
|
+
self._data = self._data[nextChunk + 1 :]
|
|
125
|
+
|
|
126
|
+
# If we get two heartbeats in a row, this will be false
|
|
127
|
+
if len(vortexMsg):
|
|
128
|
+
self._vortexMsgsQueue.append(vortexMsg)
|
|
129
|
+
|
|
130
|
+
if self._vortexMsgsQueue and not self._processVortexMsgs.running:
|
|
131
|
+
reactor.callLater(0, self._processVortexMsgs)
|
|
132
|
+
|
|
133
|
+
@inlineCallbacks
|
|
134
|
+
@nonConcurrentMethod
|
|
135
|
+
def _processVortexMsgs(self):
|
|
136
|
+
while self._vortexMsgsQueue:
|
|
137
|
+
vortexMsg = self._vortexMsgsQueue.popleft()
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
payloadEnvelope = yield PayloadEnvelope().fromVortexMsgDefer(
|
|
141
|
+
vortexMsg
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if payloadEnvelope.isEmpty():
|
|
145
|
+
self._processServerInfoPayload(payloadEnvelope)
|
|
146
|
+
else:
|
|
147
|
+
self._deliverPayload(payloadEnvelope)
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(vortexMsg)
|
|
151
|
+
print(e)
|
|
152
|
+
self._logger.exception(e)
|
|
153
|
+
raise
|
|
154
|
+
|
|
155
|
+
def _processServerInfoPayload(self, payload):
|
|
156
|
+
"""Process Server Info Payload
|
|
157
|
+
|
|
158
|
+
The first payload a server sends to the client contains information about it's
|
|
159
|
+
self.
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
if PayloadEnvelope.vortexUuidKey in payload.filt:
|
|
164
|
+
self._serverVortexUuid = payload.filt[PayloadEnvelope.vortexUuidKey]
|
|
165
|
+
|
|
166
|
+
if PayloadEnvelope.vortexNameKey in payload.filt:
|
|
167
|
+
self._serverVortexName = payload.filt[PayloadEnvelope.vortexNameKey]
|
|
168
|
+
|
|
169
|
+
self._nameAndUuidReceived(
|
|
170
|
+
name=self._serverVortexName, uuid=self._serverVortexUuid
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _deliverPayload(self, payloadEnvelope):
|
|
174
|
+
PayloadIO().process(
|
|
175
|
+
payloadEnvelope,
|
|
176
|
+
vortexUuid=self._serverVortexUuid,
|
|
177
|
+
vortexName=self._serverVortexName,
|
|
178
|
+
httpSession=None,
|
|
179
|
+
sendResponse=self._createResponseSenderCallable(),
|
|
180
|
+
)
|
vortex/VortexServer.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* Created by Synerty Pty Ltd
|
|
3
|
+
*
|
|
4
|
+
* This software is open source, the MIT license applies.
|
|
5
|
+
*
|
|
6
|
+
* Website : http://www.synerty.com
|
|
7
|
+
* Support : support@synerty.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import Dict
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from typing import Union
|
|
16
|
+
from weakref import WeakValueDictionary
|
|
17
|
+
|
|
18
|
+
from twisted.internet import reactor
|
|
19
|
+
from twisted.internet import task
|
|
20
|
+
from twisted.internet.defer import Deferred
|
|
21
|
+
from twisted.internet.defer import inlineCallbacks
|
|
22
|
+
from twisted.python.components import registerAdapter
|
|
23
|
+
from twisted.web.server import Session
|
|
24
|
+
from zope.interface import Attribute
|
|
25
|
+
from zope.interface import Interface
|
|
26
|
+
from zope.interface.declarations import implementer
|
|
27
|
+
|
|
28
|
+
from .DeferUtil import isMainThread
|
|
29
|
+
from .DeferUtil import vortexLogFailure
|
|
30
|
+
from .PayloadEnvelope import PayloadEnvelope
|
|
31
|
+
from .PayloadEnvelope import VortexMsgList
|
|
32
|
+
from .PayloadFilterKeys import rapuiServerEcho
|
|
33
|
+
from .PayloadIO import PayloadIO
|
|
34
|
+
from .PayloadPriority import DEFAULT_PRIORITY
|
|
35
|
+
from .VortexABC import VortexABC
|
|
36
|
+
from .VortexABC import VortexInfo
|
|
37
|
+
from .VortexUtil import logLargeMessages
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
""" ---------------------------------------------------------------------------
|
|
42
|
+
VortexServer
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
HEART_BEAT_PERIOD = 10.0
|
|
46
|
+
HEART_BEAT_TIMEOUT = 180.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VortexServer(VortexABC):
|
|
50
|
+
"""VortexServer
|
|
51
|
+
The static instance of the controller
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
_DEBUG_LOGGING = False
|
|
55
|
+
|
|
56
|
+
def __init__(self, name: str, requiresBase64Encoding=True) -> None:
|
|
57
|
+
# Simple initialisations up the top
|
|
58
|
+
self._name = name
|
|
59
|
+
self._requiresBase64Encoding = requiresBase64Encoding
|
|
60
|
+
self._uuid = str(uuid.uuid4())
|
|
61
|
+
self._shutdown = False
|
|
62
|
+
|
|
63
|
+
# Store all our sessions
|
|
64
|
+
self._httpSessionsBySessionUuid: WeakValueDictionary = (
|
|
65
|
+
WeakValueDictionary()
|
|
66
|
+
)
|
|
67
|
+
self._connectionByVortexUuid: Dict[str, Any] = {}
|
|
68
|
+
self._remoteVortexInfo: tuple[VortexInfo] = tuple()
|
|
69
|
+
|
|
70
|
+
def name(self):
|
|
71
|
+
return self._name
|
|
72
|
+
|
|
73
|
+
def uuid(self):
|
|
74
|
+
return self._uuid
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def requiresBase64Encoding(self):
|
|
78
|
+
return self._requiresBase64Encoding
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def localVortexInfo(self) -> VortexInfo:
|
|
82
|
+
return VortexInfo(name=self._name, uuid=self._uuid)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def remoteVortexInfo(self) -> tuple[VortexInfo]:
|
|
86
|
+
return self._remoteVortexInfo
|
|
87
|
+
|
|
88
|
+
def _connectionsChanged(
|
|
89
|
+
self, vortexClosedNames: list[str], nowOnline: bool
|
|
90
|
+
):
|
|
91
|
+
vortexInfos = []
|
|
92
|
+
|
|
93
|
+
for conn in self._connectionByVortexUuid.values():
|
|
94
|
+
vortexInfos.append(
|
|
95
|
+
VortexInfo(
|
|
96
|
+
name=conn.remoteVortexName, uuid=conn.remoteVortexUuid
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self._remoteVortexInfo = tuple(vortexInfos)
|
|
101
|
+
|
|
102
|
+
from vortex.VortexFactory import VortexFactory
|
|
103
|
+
|
|
104
|
+
VortexFactory.connectionChanged()
|
|
105
|
+
|
|
106
|
+
for vortexName in vortexClosedNames:
|
|
107
|
+
# noinspection PyProtectedMember
|
|
108
|
+
VortexFactory._notifyOfVortexStatusChange(
|
|
109
|
+
vortexName, online=nowOnline
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def connections(self) -> Dict[str, Any]:
|
|
114
|
+
return self._connectionByVortexUuid
|
|
115
|
+
|
|
116
|
+
def isShutdown(self):
|
|
117
|
+
return self._shutdown
|
|
118
|
+
|
|
119
|
+
def shutdown(self):
|
|
120
|
+
self._shutdown = True
|
|
121
|
+
|
|
122
|
+
for sess in list(self._httpSessionsBySessionUuid.values()):
|
|
123
|
+
sess.expire()
|
|
124
|
+
|
|
125
|
+
for conn in list(self._connectionByVortexUuid.values()):
|
|
126
|
+
conn.close()
|
|
127
|
+
|
|
128
|
+
def vortexUuids(self):
|
|
129
|
+
return list(self._connectionByVortexUuid.keys())
|
|
130
|
+
|
|
131
|
+
def isVortexAlive(self, vortexUuid):
|
|
132
|
+
return vortexUuid in self._connectionByVortexUuid
|
|
133
|
+
|
|
134
|
+
def vortexClientIpPort(self, vortexUuid):
|
|
135
|
+
connection = self._connectionByVortexUuid.get(vortexUuid)
|
|
136
|
+
if not connection:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
return "%s:%s" % (connection.ip, connection.port)
|
|
140
|
+
|
|
141
|
+
def connectionOpened(self, httpSession, vortexConnection):
|
|
142
|
+
if self._DEBUG_LOGGING:
|
|
143
|
+
logger.debug("VortexServer - connectionOpened")
|
|
144
|
+
|
|
145
|
+
vortexUuid = vortexConnection.remoteVortexUuid
|
|
146
|
+
assert vortexUuid
|
|
147
|
+
|
|
148
|
+
if httpSession:
|
|
149
|
+
# If this is a new session, Make sure we have an expire callback on it
|
|
150
|
+
if httpSession.uid not in self._httpSessionsBySessionUuid:
|
|
151
|
+
self._httpSessionsBySessionUuid[httpSession.uid] = httpSession
|
|
152
|
+
httpSession.notifyOnExpire(
|
|
153
|
+
lambda: self._sessionExpired(httpSession.uid)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Update the connection dict in the sessions object
|
|
157
|
+
httpSessionConns = VortexSessionI(httpSession).connections
|
|
158
|
+
httpSessionConns[vortexUuid] = vortexConnection
|
|
159
|
+
|
|
160
|
+
# Get the old connection if it exists
|
|
161
|
+
if vortexUuid in self._connectionByVortexUuid:
|
|
162
|
+
self._connectionByVortexUuid[vortexUuid].close()
|
|
163
|
+
|
|
164
|
+
# Update the _connectionsByvortexUuid
|
|
165
|
+
self._connectionByVortexUuid[vortexUuid] = vortexConnection
|
|
166
|
+
self._connectionsChanged(
|
|
167
|
+
[vortexConnection.remoteVortexName], nowOnline=True
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def connectionClosed(self, conn):
|
|
171
|
+
if self._DEBUG_LOGGING:
|
|
172
|
+
logger.debug("VortexServer - connectionClosed")
|
|
173
|
+
|
|
174
|
+
vortexUuid = conn.remoteVortexUuid
|
|
175
|
+
|
|
176
|
+
if conn.httpSessionUuid in self._httpSessionsBySessionUuid:
|
|
177
|
+
session = self._httpSessionsBySessionUuid[conn.httpSessionUuid]
|
|
178
|
+
assert session
|
|
179
|
+
|
|
180
|
+
# cleanup _sessionsBySessionUuid
|
|
181
|
+
conns = VortexSessionI(session).connections
|
|
182
|
+
if conns[vortexUuid] == conn:
|
|
183
|
+
del conns[vortexUuid]
|
|
184
|
+
|
|
185
|
+
# cleanup _connectionsByvortexUuid
|
|
186
|
+
vortexClosedNames = []
|
|
187
|
+
if conn.remoteVortexUuid in self._connectionByVortexUuid:
|
|
188
|
+
if self._connectionByVortexUuid[conn.remoteVortexUuid] == conn:
|
|
189
|
+
del self._connectionByVortexUuid[conn.remoteVortexUuid]
|
|
190
|
+
vortexClosedNames.append(conn.remoteVortexName)
|
|
191
|
+
|
|
192
|
+
self._connectionsChanged(vortexClosedNames, nowOnline=False)
|
|
193
|
+
|
|
194
|
+
def _sessionExpired(self, httpSessionUuid):
|
|
195
|
+
logger.debug(
|
|
196
|
+
"VortexServer - _sessionExpired, Session %s has expired"
|
|
197
|
+
% httpSessionUuid
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# cleanup _sessionsBySessionUuid
|
|
201
|
+
del self._httpSessionsBySessionUuid[httpSessionUuid]
|
|
202
|
+
|
|
203
|
+
# cleanup _connectionsByvortexUuid
|
|
204
|
+
vortexClosedNames = []
|
|
205
|
+
for vortexUuid, conn in list(self._connectionByVortexUuid.items()):
|
|
206
|
+
if conn.httpSessionUuid == httpSessionUuid:
|
|
207
|
+
del self._connectionByVortexUuid[vortexUuid]
|
|
208
|
+
vortexClosedNames.append(conn.remoteVortexName)
|
|
209
|
+
|
|
210
|
+
self._connectionsChanged(vortexClosedNames, nowOnline=False)
|
|
211
|
+
|
|
212
|
+
def payloadReveived(self, httpSession, vortexUuid, vortexName, payload):
|
|
213
|
+
# print "VortexServer - payloadReveived"
|
|
214
|
+
|
|
215
|
+
if rapuiServerEcho in payload.filt:
|
|
216
|
+
payload.filt.pop(rapuiServerEcho)
|
|
217
|
+
self.sendVortexMsg(payload.toVortexMsg(), vortexUuid)
|
|
218
|
+
|
|
219
|
+
def sendResponse(
|
|
220
|
+
vortexMsg: bytes, priority: int = DEFAULT_PRIORITY
|
|
221
|
+
) -> Deferred:
|
|
222
|
+
"""Send Back
|
|
223
|
+
|
|
224
|
+
Sends a response back to where this payload come from.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
return self.sendVortexMsg(
|
|
228
|
+
vortexMsg, vortexUuid=vortexUuid, priority=priority
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
PayloadIO().process(
|
|
232
|
+
payload,
|
|
233
|
+
vortexUuid=vortexUuid,
|
|
234
|
+
vortexName=vortexName,
|
|
235
|
+
httpSession=httpSession,
|
|
236
|
+
sendResponse=sendResponse,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def sendVortexMsg(
|
|
240
|
+
self,
|
|
241
|
+
vortexMsgs: Union[VortexMsgList, bytes, None] = None,
|
|
242
|
+
vortexUuid: Optional[str] = None,
|
|
243
|
+
priority: int = DEFAULT_PRIORITY,
|
|
244
|
+
):
|
|
245
|
+
"""Send Vortex Msg
|
|
246
|
+
|
|
247
|
+
Sends the vortex message to any conencted clients with vortexUuid.
|
|
248
|
+
Or broadcast it to all connected vortex clients if it's None
|
|
249
|
+
|
|
250
|
+
:param vortexMsgs: The vortex message to send
|
|
251
|
+
:param vortexUuid: The vortexUuid of the client to send to.
|
|
252
|
+
"""
|
|
253
|
+
if vortexMsgs is None:
|
|
254
|
+
vortexMsgs = [PayloadEnvelope().toVortexMsg()]
|
|
255
|
+
|
|
256
|
+
logLargeMessages(logger, vortexMsgs, vortexUuid)
|
|
257
|
+
|
|
258
|
+
if isMainThread():
|
|
259
|
+
return self._sendVortexMsgLater(
|
|
260
|
+
vortexMsgs, vortexUuid=vortexUuid, priority=priority
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return task.deferLater(
|
|
264
|
+
reactor,
|
|
265
|
+
0,
|
|
266
|
+
self._sendVortexMsgLater,
|
|
267
|
+
vortexMsgs,
|
|
268
|
+
vortexUuid=vortexUuid,
|
|
269
|
+
priority=priority,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@inlineCallbacks
|
|
273
|
+
def _sendVortexMsgLater(
|
|
274
|
+
self,
|
|
275
|
+
vortexMsgs: Union[VortexMsgList, bytes],
|
|
276
|
+
vortexUuid: Optional[str],
|
|
277
|
+
priority: int,
|
|
278
|
+
):
|
|
279
|
+
"""Send the message.
|
|
280
|
+
|
|
281
|
+
Send it later,
|
|
282
|
+
This also means it doesn't matter what thread this is called from
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
from vortex.VortexFactory import VortexFactory
|
|
286
|
+
|
|
287
|
+
yield None
|
|
288
|
+
|
|
289
|
+
if not isinstance(vortexMsgs, list):
|
|
290
|
+
vortexMsgs = [vortexMsgs]
|
|
291
|
+
|
|
292
|
+
# Deliver locally
|
|
293
|
+
if vortexUuid == self._uuid:
|
|
294
|
+
for vortexMsg in vortexMsgs:
|
|
295
|
+
|
|
296
|
+
def sendResponse(vortexMsg_, priority_=DEFAULT_PRIORITY):
|
|
297
|
+
self._sendVortexMsgLater(vortexMsg_, self._uuid, priority_)
|
|
298
|
+
|
|
299
|
+
def cb(payloadEnvelope: PayloadEnvelope) -> None:
|
|
300
|
+
PayloadIO().process(
|
|
301
|
+
payloadEnvelope,
|
|
302
|
+
vortexUuid=self._uuid,
|
|
303
|
+
vortexName=self._name,
|
|
304
|
+
httpSession=None,
|
|
305
|
+
sendResponse=sendResponse,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
d = PayloadEnvelope.fromVortexMsgDefer(vortexMsg)
|
|
309
|
+
d.addCallback(cb)
|
|
310
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
311
|
+
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
# If any transports require base64 encoding, then encode them all
|
|
315
|
+
if self.requiresBase64Encoding:
|
|
316
|
+
for index, vortexMsg in enumerate(vortexMsgs):
|
|
317
|
+
if vortexMsg.startswith(b"{"):
|
|
318
|
+
vortexMsg = yield PayloadEnvelope.base64EncodeDefer(
|
|
319
|
+
vortexMsg
|
|
320
|
+
)
|
|
321
|
+
vortexMsgs[index] = vortexMsg + b"."
|
|
322
|
+
|
|
323
|
+
if vortexUuid is None:
|
|
324
|
+
conns = self._connectionByVortexUuid.values()
|
|
325
|
+
elif vortexUuid in self._connectionByVortexUuid:
|
|
326
|
+
conns = [self._connectionByVortexUuid[vortexUuid]]
|
|
327
|
+
else:
|
|
328
|
+
logger.debug(
|
|
329
|
+
"Vortex %s offline, not sending message of size %s",
|
|
330
|
+
vortexUuid,
|
|
331
|
+
", ".join(["{:,}".format(len(m)) for m in vortexMsgs]),
|
|
332
|
+
)
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
for conn in conns:
|
|
336
|
+
for vortexMsg in vortexMsgs:
|
|
337
|
+
|
|
338
|
+
# noinspection PyProtectedMember
|
|
339
|
+
if VortexFactory._DEBUG_LOGGING:
|
|
340
|
+
logger.debug(
|
|
341
|
+
"Sending vortexMsg to %s", conn.remoteVortexUuid
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
conn.write(vortexMsg, priority)
|
|
345
|
+
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
""" ---------------------------------------------------------------------------
|
|
350
|
+
VortexServer Session
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class VortexSessionI(Interface):
|
|
355
|
+
connections: Dict = Attribute(
|
|
356
|
+
"VortexServer connections for this session, by window uuid"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@implementer(VortexSessionI)
|
|
361
|
+
class VortexSession(object):
|
|
362
|
+
def __init__(self, session):
|
|
363
|
+
session.sessionTimeout = HEART_BEAT_TIMEOUT
|
|
364
|
+
self.connections = {}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
registerAdapter(VortexSession, Session, VortexSessionI)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* Created by Synerty Pty Ltd
|
|
3
|
+
*
|
|
4
|
+
* This software is open source, the MIT license applies.
|
|
5
|
+
*
|
|
6
|
+
* Website : http://www.synerty.com
|
|
7
|
+
* Support : support@synerty.com
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import pytz
|
|
13
|
+
from twisted.internet import task
|
|
14
|
+
|
|
15
|
+
from .PayloadPriority import DEFAULT_PRIORITY
|
|
16
|
+
from .VortexConnectionABC import VortexConnectionABC
|
|
17
|
+
from .VortexServer import VortexServer, HEART_BEAT_PERIOD, HEART_BEAT_TIMEOUT
|
|
18
|
+
from .VortexWritePushProducer import VortexWritePushProducer
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(name=__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VortexServerConnection(VortexConnectionABC):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
vortexServer: VortexServer,
|
|
27
|
+
remoteVortexUuid: str,
|
|
28
|
+
remoteVortexName: str,
|
|
29
|
+
httpSession,
|
|
30
|
+
transport,
|
|
31
|
+
addr,
|
|
32
|
+
) -> None:
|
|
33
|
+
VortexConnectionABC.__init__(
|
|
34
|
+
self,
|
|
35
|
+
logger,
|
|
36
|
+
vortexServer,
|
|
37
|
+
remoteVortexUuid=remoteVortexUuid,
|
|
38
|
+
remoteVortexName=remoteVortexName,
|
|
39
|
+
httpSessionUuid=httpSession,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self._lastHeartBeatTime = datetime.now(pytz.utc)
|
|
43
|
+
self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
|
|
44
|
+
|
|
45
|
+
self._transport = transport
|
|
46
|
+
self._addr = addr
|
|
47
|
+
|
|
48
|
+
# Start our heart beat
|
|
49
|
+
self._beatLoopingCall = task.LoopingCall(self._beat)
|
|
50
|
+
d = self._beatLoopingCall.start(HEART_BEAT_PERIOD, now=False)
|
|
51
|
+
d.addErrback(lambda f: logger.exception(f.value))
|
|
52
|
+
|
|
53
|
+
self._producer = None
|
|
54
|
+
|
|
55
|
+
# Register the producer if there isn't one already.
|
|
56
|
+
# The websocket server already has one.
|
|
57
|
+
if not self._transport.producer:
|
|
58
|
+
self._producer = VortexWritePushProducer(
|
|
59
|
+
transport, lambda: self.close(), remoteVortexName
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
transport.registerProducer(self._producer, True)
|
|
63
|
+
|
|
64
|
+
def beatReceived(self):
|
|
65
|
+
self._lastHeartBeatTime = datetime.now(pytz.utc)
|
|
66
|
+
|
|
67
|
+
def _beat(self):
|
|
68
|
+
# If we're closed, do nothing
|
|
69
|
+
if self._closed:
|
|
70
|
+
if self._beatLoopingCall.running:
|
|
71
|
+
self._beatLoopingCall.stop()
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
beatTimeout = (
|
|
75
|
+
datetime.now(pytz.utc) - self._lastHeartBeatTime
|
|
76
|
+
).seconds > HEART_BEAT_TIMEOUT
|
|
77
|
+
|
|
78
|
+
# If we've been asleep, then make note of that (VM suspended)
|
|
79
|
+
checkTimout = (
|
|
80
|
+
datetime.now(pytz.utc) - self._lastHeartBeatCheckTime
|
|
81
|
+
).seconds > HEART_BEAT_TIMEOUT
|
|
82
|
+
|
|
83
|
+
# Mark that we've just checked it
|
|
84
|
+
self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
|
|
85
|
+
|
|
86
|
+
if checkTimout:
|
|
87
|
+
self._lastHeartBeatTime = datetime.now(pytz.utc)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# If we havn't heard from the client, then close the connection
|
|
91
|
+
if beatTimeout:
|
|
92
|
+
self._beatLoopingCall.stop()
|
|
93
|
+
self._timedOut = True
|
|
94
|
+
self.close()
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
self._write(b".", DEFAULT_PRIORITY)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def ip(self):
|
|
101
|
+
return self._addr.host
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def port(self):
|
|
105
|
+
return self._addr.port
|
|
106
|
+
|
|
107
|
+
def write(self, payloadVortexStr: bytes, priority: int = DEFAULT_PRIORITY):
|
|
108
|
+
assert not self._closed
|
|
109
|
+
self._write(payloadVortexStr, priority)
|
|
110
|
+
|
|
111
|
+
def _write(self, payloadVortexStr: bytes, priority: int):
|
|
112
|
+
if self._producer:
|
|
113
|
+
self._producer.write(payloadVortexStr, priority)
|
|
114
|
+
else:
|
|
115
|
+
self._transport.write(payloadVortexStr)
|
|
116
|
+
|
|
117
|
+
def close(self):
|
|
118
|
+
if self._beatLoopingCall.running:
|
|
119
|
+
self._beatLoopingCall.stop()
|
|
120
|
+
|
|
121
|
+
self._transport.loseConnection()
|
|
122
|
+
|
|
123
|
+
def transportClosed(self):
|
|
124
|
+
if self._producer and hasattr(self._producer, "close"):
|
|
125
|
+
self._producer.close()
|
|
126
|
+
self._producer = None
|
|
127
|
+
VortexConnectionABC.close(self)
|