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,193 @@
|
|
|
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 collections
|
|
11
|
+
import logging
|
|
12
|
+
from _pyio import BytesIO
|
|
13
|
+
from typing import Deque
|
|
14
|
+
|
|
15
|
+
from twisted.internet import task
|
|
16
|
+
from twisted.internet.defer import inlineCallbacks
|
|
17
|
+
from twisted.web.resource import Resource
|
|
18
|
+
from twisted.web.server import NOT_DONE_YET
|
|
19
|
+
|
|
20
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
21
|
+
from vortex.VortexServer import HEART_BEAT_PERIOD
|
|
22
|
+
from .PayloadPriority import DEFAULT_PRIORITY
|
|
23
|
+
from .VortexConnectionABC import VortexConnectionABC
|
|
24
|
+
from .VortexServer import VortexServer
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(name=__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VortexServerHttpResource(Resource):
|
|
30
|
+
"""VortexServer Resource
|
|
31
|
+
This resource is the server endpoint for the vortex.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
isLeaf = True
|
|
35
|
+
|
|
36
|
+
def __init__(self, vortex: VortexServer) -> None:
|
|
37
|
+
Resource.__init__(self)
|
|
38
|
+
self.__vortex = vortex
|
|
39
|
+
|
|
40
|
+
def render_GET(self, request):
|
|
41
|
+
request.code = 405
|
|
42
|
+
return b"You have reached the vortex. It only likes POST methods."
|
|
43
|
+
|
|
44
|
+
def render_POST(self, request):
|
|
45
|
+
|
|
46
|
+
from vortex.VortexFactory import VortexFactory
|
|
47
|
+
|
|
48
|
+
if not VortexFactory.canConnect(str(request.client.host)):
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
remoteVortexUuid = request.args[b"vortexUuid"][0].decode()
|
|
52
|
+
remoteVortexName = request.args[b"vortexName"][0].decode()
|
|
53
|
+
|
|
54
|
+
if self.__vortex.isShutdown():
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
httpSession = request.getSession()
|
|
58
|
+
conn = VortexResourceConnection(
|
|
59
|
+
self.__vortex, remoteVortexUuid, remoteVortexName, request
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Send a heart beat down the new connection, tell it who we are.
|
|
63
|
+
connectPayloadFilt = {}
|
|
64
|
+
connectPayloadFilt[PayloadEnvelope.vortexUuidKey] = self.__vortex.uuid()
|
|
65
|
+
connectPayloadFilt[PayloadEnvelope.vortexNameKey] = self.__vortex.name()
|
|
66
|
+
conn.write(PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg())
|
|
67
|
+
|
|
68
|
+
data = request.content.read()
|
|
69
|
+
if len(data):
|
|
70
|
+
for vortexStr in data.strip(b".").split(b"."):
|
|
71
|
+
self._processVortexMsg(
|
|
72
|
+
httpSession, conn, vortexStr.decode("UTF-8")
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Request will be around for a while, do some cleanups
|
|
76
|
+
request.content = BytesIO()
|
|
77
|
+
request.args = {}
|
|
78
|
+
|
|
79
|
+
self.__vortex.connectionOpened(httpSession, conn)
|
|
80
|
+
|
|
81
|
+
def connClosed(err):
|
|
82
|
+
logger.debug("VortexServer connection ended by client")
|
|
83
|
+
self.__vortex.connectionClosed(conn)
|
|
84
|
+
|
|
85
|
+
request.notifyFinish().addErrback(connClosed)
|
|
86
|
+
|
|
87
|
+
return NOT_DONE_YET
|
|
88
|
+
|
|
89
|
+
@inlineCallbacks
|
|
90
|
+
def _processVortexMsg(self, httpSession, conn, vortexMsg):
|
|
91
|
+
payload = yield PayloadEnvelope().fromVortexMsgDefer(vortexMsg)
|
|
92
|
+
self.__vortex.payloadReveived(
|
|
93
|
+
httpSession=httpSession,
|
|
94
|
+
vortexUuid=conn.remoteVortexUuid,
|
|
95
|
+
vortexName=conn.remoteVortexName,
|
|
96
|
+
payload=payload,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class VortexResourceConnection(VortexConnectionABC):
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
vortexServer: VortexServer,
|
|
104
|
+
remoteVortexUuid: str,
|
|
105
|
+
remoteVortexName: str,
|
|
106
|
+
request,
|
|
107
|
+
) -> None:
|
|
108
|
+
VortexConnectionABC.__init__(
|
|
109
|
+
self,
|
|
110
|
+
logger,
|
|
111
|
+
vortexServer,
|
|
112
|
+
remoteVortexUuid=remoteVortexUuid,
|
|
113
|
+
remoteVortexName=remoteVortexName,
|
|
114
|
+
httpSessionUuid=request.getSession().uid,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
self._request = request
|
|
118
|
+
self._request.responseHeaders.setRawHeaders(
|
|
119
|
+
b"content-type", [b"text/text"]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self._buffer: Deque[bytes] = collections.deque()
|
|
123
|
+
|
|
124
|
+
self._requestReadyCheckTimer = task.LoopingCall(
|
|
125
|
+
self._checkIfRequestIsReady
|
|
126
|
+
)
|
|
127
|
+
self._requestReadyCheckTimer.start(0.001)
|
|
128
|
+
|
|
129
|
+
# Start our heart beat
|
|
130
|
+
self._beatLoopingCall = task.LoopingCall(self._beat)
|
|
131
|
+
d = self._beatLoopingCall.start(HEART_BEAT_PERIOD)
|
|
132
|
+
d.addErrback(lambda f: logger.exception(f.value))
|
|
133
|
+
|
|
134
|
+
def _beat(self):
|
|
135
|
+
if (
|
|
136
|
+
self._closed
|
|
137
|
+
or self._request.finished
|
|
138
|
+
or self._request.channel is None
|
|
139
|
+
):
|
|
140
|
+
self._beatLoopingCall.stop()
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# Send the heartbeats
|
|
144
|
+
self._request.write(b".")
|
|
145
|
+
|
|
146
|
+
# Touch the session
|
|
147
|
+
self._request.getSession().touch()
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def ip(self):
|
|
151
|
+
return self._request.client.host
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def port(self):
|
|
155
|
+
return self._request.client.port
|
|
156
|
+
|
|
157
|
+
def _checkIfRequestIsReady(self):
|
|
158
|
+
# This seems to be the fingerprint of the fact that request will not write
|
|
159
|
+
# our data yet.
|
|
160
|
+
if self._request.chunked and not self._request.startedWriting:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if self._requestReadyCheckTimer.running:
|
|
164
|
+
self._requestReadyCheckTimer.stop()
|
|
165
|
+
|
|
166
|
+
while self._buffer:
|
|
167
|
+
self._write(self._buffer.popleft())
|
|
168
|
+
self._buffer = None
|
|
169
|
+
|
|
170
|
+
def write(self, payloadVortexStr: bytes, priority: int = DEFAULT_PRIORITY):
|
|
171
|
+
assert not self._closed
|
|
172
|
+
# If we have a buffer, write it.
|
|
173
|
+
if self._buffer:
|
|
174
|
+
self._buffer.append(payloadVortexStr)
|
|
175
|
+
else:
|
|
176
|
+
self._write(payloadVortexStr)
|
|
177
|
+
|
|
178
|
+
def _write(self, payloadVortexStr: bytes):
|
|
179
|
+
if self._request.finished or self._request.channel is None:
|
|
180
|
+
logger.error("VORTEX CLOSED, CAN'T WRITE PAYLOAD")
|
|
181
|
+
self._closed = True
|
|
182
|
+
self._vortexServer.connectionClosed(self)
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
self._request.write(payloadVortexStr)
|
|
186
|
+
self._request.write(b".")
|
|
187
|
+
|
|
188
|
+
def close(self):
|
|
189
|
+
if self._beatLoopingCall.running:
|
|
190
|
+
self._beatLoopingCall.stop()
|
|
191
|
+
|
|
192
|
+
self._closed = True
|
|
193
|
+
self._request.finish()
|
|
@@ -0,0 +1,104 @@
|
|
|
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 typing import Union
|
|
11
|
+
|
|
12
|
+
from twisted.internet.defer import succeed
|
|
13
|
+
from twisted.internet.protocol import connectionDone, Factory
|
|
14
|
+
|
|
15
|
+
from vortex.VortexPayloadProtocol import VortexPayloadProtocol
|
|
16
|
+
from vortex.VortexServerConnection import VortexServerConnection
|
|
17
|
+
from .PayloadEnvelope import VortexMsgList, PayloadEnvelope
|
|
18
|
+
from .PayloadPriority import DEFAULT_PRIORITY
|
|
19
|
+
from .VortexServer import VortexServer
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(name=__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VortexTcpServerProtocol(VortexPayloadProtocol):
|
|
25
|
+
def __init__(self, vortex: VortexServer, addr) -> None:
|
|
26
|
+
VortexPayloadProtocol.__init__(self, logger)
|
|
27
|
+
self._vortex = vortex
|
|
28
|
+
self._addr = addr
|
|
29
|
+
|
|
30
|
+
self._conn = None
|
|
31
|
+
|
|
32
|
+
self._dataBuffer = b""
|
|
33
|
+
self._remoteVortexUuid = None
|
|
34
|
+
self._remoteVortexName = None
|
|
35
|
+
self._httpSession = None
|
|
36
|
+
|
|
37
|
+
def _beat(self):
|
|
38
|
+
if self._conn:
|
|
39
|
+
self._conn.beatReceived()
|
|
40
|
+
|
|
41
|
+
def _nameAndUuidReceived(self, name, uuid):
|
|
42
|
+
self._remoteVortexUuid = uuid
|
|
43
|
+
self._remoteVortexName = name
|
|
44
|
+
self._conn = VortexServerConnection(
|
|
45
|
+
self._vortex,
|
|
46
|
+
self._remoteVortexUuid,
|
|
47
|
+
self._remoteVortexName,
|
|
48
|
+
self._httpSession,
|
|
49
|
+
self.transport,
|
|
50
|
+
self._addr,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Send a heart beat down the new connection, tell it who we are.
|
|
54
|
+
connectPayloadFilt = {}
|
|
55
|
+
connectPayloadFilt[PayloadEnvelope.vortexUuidKey] = self._vortex.uuid()
|
|
56
|
+
connectPayloadFilt[PayloadEnvelope.vortexNameKey] = self._vortex.name()
|
|
57
|
+
self._conn.write(PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg())
|
|
58
|
+
|
|
59
|
+
self._vortex.connectionOpened(self._httpSession, self._conn)
|
|
60
|
+
|
|
61
|
+
def _createResponseSenderCallable(self):
|
|
62
|
+
def sendResponse(
|
|
63
|
+
vortexMsgs: Union[VortexMsgList, bytes],
|
|
64
|
+
priority: int = DEFAULT_PRIORITY,
|
|
65
|
+
):
|
|
66
|
+
if isinstance(vortexMsgs, bytes):
|
|
67
|
+
vortexMsgs = [vortexMsgs]
|
|
68
|
+
|
|
69
|
+
for vortexMsg in vortexMsgs:
|
|
70
|
+
self._conn.write(vortexMsg, priority)
|
|
71
|
+
|
|
72
|
+
return succeed(True)
|
|
73
|
+
|
|
74
|
+
return sendResponse
|
|
75
|
+
|
|
76
|
+
def dataReceived(self, data):
|
|
77
|
+
|
|
78
|
+
if self._vortex.isShutdown():
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
VortexPayloadProtocol.dataReceived(self, data)
|
|
82
|
+
|
|
83
|
+
def connectionLost(self, reason=connectionDone):
|
|
84
|
+
if self._conn:
|
|
85
|
+
self._conn.transportClosed()
|
|
86
|
+
|
|
87
|
+
VortexPayloadProtocol.connectionLost(self, reason)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class VortexTcpServerFactory(Factory):
|
|
91
|
+
protocol = None
|
|
92
|
+
|
|
93
|
+
def __init__(self, vortexServer: VortexServer) -> None:
|
|
94
|
+
self._vortexServer = vortexServer
|
|
95
|
+
|
|
96
|
+
def buildProtocol(self, addr):
|
|
97
|
+
from vortex.VortexFactory import VortexFactory
|
|
98
|
+
|
|
99
|
+
if not VortexFactory.canConnect(str(addr)):
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
p = VortexTcpServerProtocol(self._vortexServer, addr)
|
|
103
|
+
p.factory = self
|
|
104
|
+
return p
|
|
@@ -0,0 +1,287 @@
|
|
|
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 collections import deque
|
|
12
|
+
from random import random
|
|
13
|
+
from urllib.parse import parse_qs
|
|
14
|
+
from urllib.parse import urlparse
|
|
15
|
+
|
|
16
|
+
from twisted.internet import reactor
|
|
17
|
+
from twisted.internet.defer import inlineCallbacks
|
|
18
|
+
from twisted.internet.protocol import Factory
|
|
19
|
+
from twisted.internet.protocol import Protocol
|
|
20
|
+
from twisted.internet.protocol import connectionDone
|
|
21
|
+
from twisted.protocols.policies import WrappingFactory
|
|
22
|
+
from twisted.web import resource
|
|
23
|
+
from twisted.web.server import NOT_DONE_YET
|
|
24
|
+
|
|
25
|
+
from txwebsocket.txws import WebSocketProtocol
|
|
26
|
+
from vortex.DeferUtil import vortexLogFailure, nonConcurrentMethod
|
|
27
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
28
|
+
from vortex.VortexServerConnection import VortexServerConnection
|
|
29
|
+
from .PayloadPriority import DEFAULT_PRIORITY
|
|
30
|
+
from .VortexServer import VortexServer
|
|
31
|
+
from .VortexWritePushProducer import VortexWritePushProducer
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(name=__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class VortexWebsocketServerProtocol(Protocol):
|
|
37
|
+
_DEBUG_LOGGING = False
|
|
38
|
+
|
|
39
|
+
def __init__(self, vortex: VortexServer, addr, httpSession=None) -> None:
|
|
40
|
+
self._vortex = vortex
|
|
41
|
+
self._addr = addr
|
|
42
|
+
self._logger = logger
|
|
43
|
+
|
|
44
|
+
self._conn = None
|
|
45
|
+
|
|
46
|
+
self._remoteVortexUuid = None
|
|
47
|
+
self._remoteVortexName = None
|
|
48
|
+
self._httpSession = httpSession
|
|
49
|
+
|
|
50
|
+
def httpSessionExpiredCallback():
|
|
51
|
+
self._httpSession = None
|
|
52
|
+
self.transport.loseConnection()
|
|
53
|
+
|
|
54
|
+
if self._httpSession:
|
|
55
|
+
self._httpSession.notifyOnExpire(httpSessionExpiredCallback)
|
|
56
|
+
|
|
57
|
+
# Most messages don't need a buffer, but websockets can split messages
|
|
58
|
+
# around the 128kb mark
|
|
59
|
+
self._data = b""
|
|
60
|
+
|
|
61
|
+
self._vortexMsgsQueue = deque()
|
|
62
|
+
|
|
63
|
+
def __initConnection(self):
|
|
64
|
+
# self.transport.setBinaryMode(True)
|
|
65
|
+
|
|
66
|
+
params = parse_qs(urlparse(self.transport.location).query)
|
|
67
|
+
|
|
68
|
+
if "vortexUuid" not in params or "vortexName" not in params:
|
|
69
|
+
raise Exception(
|
|
70
|
+
"This isn't a vortex capable websocket. Check the URL"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self._remoteVortexUuid = params["vortexUuid"][0]
|
|
74
|
+
self._remoteVortexName = params["vortexName"][0]
|
|
75
|
+
self._conn = VortexServerConnection(
|
|
76
|
+
self._vortex,
|
|
77
|
+
self._remoteVortexUuid,
|
|
78
|
+
self._remoteVortexName,
|
|
79
|
+
self._httpSession,
|
|
80
|
+
self.transport,
|
|
81
|
+
self._addr,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# noinspection PyProtectedMember
|
|
85
|
+
assert isinstance(self.transport, WebSocketProtocol) and isinstance(
|
|
86
|
+
self.transport._producerForWrite, VortexWritePushProducer
|
|
87
|
+
), "The Websocket transport is not how we expected it."
|
|
88
|
+
|
|
89
|
+
self.transport._producerForWrite.setRemoteVortexName(
|
|
90
|
+
f"{self._remoteVortexName}({self._remoteVortexUuid})"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Send a heart beat down the new connection, tell it who we are.
|
|
94
|
+
connectPayloadFilt = {}
|
|
95
|
+
connectPayloadFilt[PayloadEnvelope.vortexUuidKey] = self._vortex.uuid()
|
|
96
|
+
connectPayloadFilt[PayloadEnvelope.vortexNameKey] = self._vortex.name()
|
|
97
|
+
self._conn.write(
|
|
98
|
+
PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg(),
|
|
99
|
+
DEFAULT_PRIORITY,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self._vortex.connectionOpened(self._httpSession, self._conn)
|
|
103
|
+
|
|
104
|
+
def dataReceived(self, data):
|
|
105
|
+
if not self._conn:
|
|
106
|
+
self.__initConnection()
|
|
107
|
+
|
|
108
|
+
if self._vortex.isShutdown():
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
if self._httpSession:
|
|
112
|
+
self._httpSession.touch()
|
|
113
|
+
|
|
114
|
+
self._conn.beatReceived()
|
|
115
|
+
|
|
116
|
+
if data.startswith(b"{"):
|
|
117
|
+
# If the data starts with a { then the payloadEnvelope is al in
|
|
118
|
+
# the websocket message.
|
|
119
|
+
assert data.endswith(b"}"), (
|
|
120
|
+
"%s payloadEnvelope doesn't fit in websocket message" % __name__
|
|
121
|
+
)
|
|
122
|
+
self._vortexMsgsQueue.append(data)
|
|
123
|
+
else:
|
|
124
|
+
# Else it's a base64 encoded payloadEnvelope, and it will be separated
|
|
125
|
+
# by b'.'
|
|
126
|
+
self._data += data
|
|
127
|
+
|
|
128
|
+
self._processData()
|
|
129
|
+
|
|
130
|
+
def connectionLost(self, reason=connectionDone):
|
|
131
|
+
if self._DEBUG_LOGGING:
|
|
132
|
+
logger.debug(
|
|
133
|
+
"VortexWebsocketServerProtocol.connectionLost reason=%s", reason
|
|
134
|
+
)
|
|
135
|
+
self._processData()
|
|
136
|
+
if self._conn:
|
|
137
|
+
self._conn.transportClosed()
|
|
138
|
+
self._vortex.connectionClosed(self._conn)
|
|
139
|
+
|
|
140
|
+
def _processData(self):
|
|
141
|
+
def getNextChunkIter():
|
|
142
|
+
try:
|
|
143
|
+
while True:
|
|
144
|
+
yield self._data.index(b".")
|
|
145
|
+
|
|
146
|
+
except ValueError:
|
|
147
|
+
# There is no '.' in it, wait for more data.
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if self._data:
|
|
151
|
+
for nextChunk in getNextChunkIter():
|
|
152
|
+
vortexMsg = self._data[:nextChunk]
|
|
153
|
+
self._data = self._data[nextChunk + 1 :]
|
|
154
|
+
|
|
155
|
+
# If we get two heartbeats in a row, this will be false
|
|
156
|
+
if len(vortexMsg):
|
|
157
|
+
self._vortexMsgsQueue.append(vortexMsg)
|
|
158
|
+
|
|
159
|
+
if self._vortexMsgsQueue and not self._processVortexMsgs.running:
|
|
160
|
+
reactor.callLater(0, self._processVortexMsgs)
|
|
161
|
+
|
|
162
|
+
@inlineCallbacks
|
|
163
|
+
@nonConcurrentMethod
|
|
164
|
+
def _processVortexMsgs(self):
|
|
165
|
+
while self._vortexMsgsQueue:
|
|
166
|
+
vortexMsg = self._vortexMsgsQueue.popleft()
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
payloadEnvelope = yield PayloadEnvelope().fromVortexMsgDefer(
|
|
170
|
+
vortexMsg
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
self._deliverPayload(payloadEnvelope)
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(vortexMsg)
|
|
177
|
+
print(e)
|
|
178
|
+
self._logger.exception(e)
|
|
179
|
+
raise
|
|
180
|
+
|
|
181
|
+
def _deliverPayload(self, payloadEnvelope):
|
|
182
|
+
self._vortex.payloadReveived(
|
|
183
|
+
httpSession=self._httpSession,
|
|
184
|
+
vortexUuid=self._remoteVortexUuid,
|
|
185
|
+
vortexName=self._remoteVortexName,
|
|
186
|
+
payload=payloadEnvelope,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class VortexWebsocketServerFactory(Factory):
|
|
191
|
+
protocol = None
|
|
192
|
+
|
|
193
|
+
def __init__(self, vortexServer: VortexServer) -> None:
|
|
194
|
+
self._vortexServer = vortexServer
|
|
195
|
+
|
|
196
|
+
def buildProtocol(self, addr, httpSession=None):
|
|
197
|
+
# VortexWebSocketUpgradeResource provides this check
|
|
198
|
+
# if we're connected via a HTTP upgrade
|
|
199
|
+
if not httpSession:
|
|
200
|
+
from vortex.VortexFactory import VortexFactory
|
|
201
|
+
|
|
202
|
+
if not VortexFactory.canConnect(str(addr)):
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
p = VortexWebsocketServerProtocol(self._vortexServer, addr, httpSession)
|
|
206
|
+
p.factory = self
|
|
207
|
+
return p
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class VortexWrappedWebSocketFactory(WrappingFactory):
|
|
211
|
+
"""
|
|
212
|
+
Factory which wraps another factory to provide WebSockets transports for
|
|
213
|
+
all of its protocols.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
protocol = WebSocketProtocol
|
|
217
|
+
|
|
218
|
+
def buildProtocol(self, addr, httpSession):
|
|
219
|
+
return self.protocol(
|
|
220
|
+
self, self.wrappedFactory.buildProtocol(addr, httpSession)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class VortexWebSocketUpgradeResource(resource.Resource):
|
|
225
|
+
"""Vortex Websocket Upgrade Resource
|
|
226
|
+
|
|
227
|
+
If this resource is hit, it will attempt to upgrade the connection to a websocket.
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
isLeaf = 1
|
|
232
|
+
|
|
233
|
+
def __init__(self, websocketFactory):
|
|
234
|
+
"""Constructor
|
|
235
|
+
|
|
236
|
+
@:param websocketFactory: A factory that will build a WebsocketProtocol (above)
|
|
237
|
+
"""
|
|
238
|
+
resource.Resource.__init__(self)
|
|
239
|
+
self._websocketFactory = websocketFactory
|
|
240
|
+
|
|
241
|
+
def render(self, request):
|
|
242
|
+
from vortex.VortexFactory import VortexFactory
|
|
243
|
+
|
|
244
|
+
if not VortexFactory.canConnect(str(request.client.host)):
|
|
245
|
+
request.setResponseCode(503)
|
|
246
|
+
request.setHeader("Retry-After", str(max(10, int(random() * 100))))
|
|
247
|
+
return b"Rejected due to new connections throttling."
|
|
248
|
+
|
|
249
|
+
httpSession = request.getSession()
|
|
250
|
+
|
|
251
|
+
websocketProtocol = self._websocketFactory.buildProtocol(
|
|
252
|
+
request.client, httpSession
|
|
253
|
+
)
|
|
254
|
+
websocketProtocol.makeConnection(request.channel.transport)
|
|
255
|
+
websocketProtocol.initFromRequest(request)
|
|
256
|
+
request.channel.upgradeToWebsocket(websocketProtocol)
|
|
257
|
+
|
|
258
|
+
def unregister():
|
|
259
|
+
request.channel.unregisterProducer()
|
|
260
|
+
websocketProtocol.unregisterProducer()
|
|
261
|
+
|
|
262
|
+
producer = VortexWritePushProducer(
|
|
263
|
+
request.channel.transport,
|
|
264
|
+
stopProducingCallback=lambda: unregister(),
|
|
265
|
+
# The websocket frames this before this producer gets it
|
|
266
|
+
writeWholeFrames=False,
|
|
267
|
+
terminateFrameWithDot=False,
|
|
268
|
+
splitFrames=True,
|
|
269
|
+
)
|
|
270
|
+
producer.WARNING_DATA_LENGTH = 10 * 1024 * 1024
|
|
271
|
+
producer.ERROR_DATA_LENGTH = 50 * 1024 * 1024
|
|
272
|
+
|
|
273
|
+
unregister()
|
|
274
|
+
request.channel.registerProducer(producer, streaming=True)
|
|
275
|
+
websocketProtocol.registerProducer(producer, streaming=True)
|
|
276
|
+
|
|
277
|
+
def closedError(failure):
|
|
278
|
+
# logger.error("Got closedError %s" % failure)
|
|
279
|
+
websocketProtocol.connectionLost(failure.value)
|
|
280
|
+
|
|
281
|
+
def closedOk(data):
|
|
282
|
+
pass
|
|
283
|
+
# logger.debug("Got closedOk %s" % data)
|
|
284
|
+
|
|
285
|
+
request.notifyFinish().addCallbacks(closedOk, closedError)
|
|
286
|
+
|
|
287
|
+
return NOT_DONE_YET
|