socketflow 0.1.0__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.
- socketflow/__init__.py +45 -0
- socketflow/client_side/__init__.py +3 -0
- socketflow/client_side/client.py +383 -0
- socketflow/global_side/__init__.py +13 -0
- socketflow/global_side/blueprint.py +118 -0
- socketflow/global_side/compression.py +155 -0
- socketflow/global_side/dispatcher.py +104 -0
- socketflow/global_side/event.py +69 -0
- socketflow/global_side/exceptions.py +100 -0
- socketflow/global_side/message_handler.py +38 -0
- socketflow/global_side/message_manager.py +98 -0
- socketflow/server_side/__init__.py +3 -0
- socketflow/server_side/server.py +407 -0
- socketflow-0.1.0.dist-info/METADATA +308 -0
- socketflow-0.1.0.dist-info/RECORD +17 -0
- socketflow-0.1.0.dist-info/WHEEL +5 -0
- socketflow-0.1.0.dist-info/top_level.txt +1 -0
socketflow/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from .global_side.event import EventType
|
|
2
|
+
from .server_side.server import TcpServer
|
|
3
|
+
from .client_side.client import TcpClient
|
|
4
|
+
from .global_side.blueprint import Blueprint
|
|
5
|
+
from .global_side.message_manager import message_manager, MessageManager
|
|
6
|
+
from .global_side.exceptions import (
|
|
7
|
+
SocketFlowException,
|
|
8
|
+
NotConnected,
|
|
9
|
+
NoResponse,
|
|
10
|
+
ConnectionTimeout,
|
|
11
|
+
KeepaliveTimeout,
|
|
12
|
+
InvalidData,
|
|
13
|
+
ProtocolError,
|
|
14
|
+
ServerError,
|
|
15
|
+
ClientError,
|
|
16
|
+
BlueprintError,
|
|
17
|
+
CompressionError,
|
|
18
|
+
MessageHandlerError,
|
|
19
|
+
DispatcherError,
|
|
20
|
+
ExceptionType,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
__all__ = [
|
|
25
|
+
"TcpServer",
|
|
26
|
+
"TcpClient",
|
|
27
|
+
"EventType",
|
|
28
|
+
"Blueprint",
|
|
29
|
+
"MessageManager",
|
|
30
|
+
"message_manager",
|
|
31
|
+
"SocketFlowException",
|
|
32
|
+
"NotConnected",
|
|
33
|
+
"NoResponse",
|
|
34
|
+
"ConnectionTimeout",
|
|
35
|
+
"KeepaliveTimeout",
|
|
36
|
+
"InvalidData",
|
|
37
|
+
"ProtocolError",
|
|
38
|
+
"ServerError",
|
|
39
|
+
"ClientError",
|
|
40
|
+
"BlueprintError",
|
|
41
|
+
"CompressionError",
|
|
42
|
+
"MessageHandlerError",
|
|
43
|
+
"DispatcherError",
|
|
44
|
+
"ExceptionType",
|
|
45
|
+
]
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import socket as socket_module
|
|
3
|
+
from ..global_side.event import (
|
|
4
|
+
EventType,
|
|
5
|
+
ConnectData,
|
|
6
|
+
DisconnectData,
|
|
7
|
+
MessageReceivedData,
|
|
8
|
+
ErrorData,
|
|
9
|
+
)
|
|
10
|
+
from ..global_side.dispatcher import EventDispatcher
|
|
11
|
+
from ..global_side.compression import MultiCompressor
|
|
12
|
+
from ..global_side.message_manager import message_manager
|
|
13
|
+
from ..global_side.message_handler import message_handler
|
|
14
|
+
from ..global_side.exceptions import ExceptionType
|
|
15
|
+
from typing import Optional, Union
|
|
16
|
+
import uuid
|
|
17
|
+
import time
|
|
18
|
+
import concurrent.futures
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TcpClientProtocol:
|
|
22
|
+
def __init__(self, client, socket):
|
|
23
|
+
self.client = client
|
|
24
|
+
self.socket = socket
|
|
25
|
+
self._buffer = bytearray()
|
|
26
|
+
self._last_ping_time = None
|
|
27
|
+
self._missed_pings = 0
|
|
28
|
+
self._ping_task = None
|
|
29
|
+
|
|
30
|
+
def handle_data(self, data):
|
|
31
|
+
"""Handle incoming data from server"""
|
|
32
|
+
self._buffer.extend(data)
|
|
33
|
+
self._missed_pings = 0
|
|
34
|
+
|
|
35
|
+
offset = 0
|
|
36
|
+
while len(self._buffer) - offset >= 4:
|
|
37
|
+
msg_len = int.from_bytes(self._buffer[offset : offset + 4], byteorder="big")
|
|
38
|
+
|
|
39
|
+
if len(self._buffer) - offset < 4 + msg_len:
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
start = offset + 4
|
|
43
|
+
end = start + msg_len
|
|
44
|
+
message_data = self._buffer[start:end]
|
|
45
|
+
|
|
46
|
+
offset = end
|
|
47
|
+
|
|
48
|
+
headers, body = message_handler.unpack_data(bytes(message_data))
|
|
49
|
+
if not headers or not isinstance(headers, dict):
|
|
50
|
+
error_msg = (
|
|
51
|
+
"Invalid message format"
|
|
52
|
+
if headers is None
|
|
53
|
+
else "Received message with invalid headers format"
|
|
54
|
+
)
|
|
55
|
+
self.client.dispatcher.emit(
|
|
56
|
+
EventType.Global.ERROR,
|
|
57
|
+
ErrorData(
|
|
58
|
+
error=ExceptionType.InvalidData(error_msg),
|
|
59
|
+
context="client.handle_data",
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
msg_type = headers.get("type")
|
|
65
|
+
if not msg_type:
|
|
66
|
+
self.client.dispatcher.emit(
|
|
67
|
+
EventType.Global.ERROR,
|
|
68
|
+
ErrorData(
|
|
69
|
+
error=ExceptionType.InvalidData(
|
|
70
|
+
"Received message without type"
|
|
71
|
+
),
|
|
72
|
+
context="client.handle_data",
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
if msg_type == "__ping__":
|
|
78
|
+
pong_message = message_handler.create_pong()
|
|
79
|
+
try:
|
|
80
|
+
self.send_data(pong_message)
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
elif msg_type == "__user__":
|
|
84
|
+
path = headers.get("path")
|
|
85
|
+
data_id = headers.get("id")
|
|
86
|
+
server_addr = (
|
|
87
|
+
self.socket.getpeername() if self.socket else ("unknown", 0)
|
|
88
|
+
)
|
|
89
|
+
event_data = MessageReceivedData(
|
|
90
|
+
data=body, server_addr=server_addr, data_id=data_id
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if data_id and data_id in self.client.pending_responses:
|
|
94
|
+
future = self.client.pending_responses.pop(data_id)
|
|
95
|
+
if hasattr(future, "set_result") and not future.done():
|
|
96
|
+
future.set_result(body)
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if path:
|
|
100
|
+
self.client.dispatcher.emit_path(path, event_data)
|
|
101
|
+
else:
|
|
102
|
+
self.client.dispatcher.emit(EventType.Client.MESSAGE, event_data)
|
|
103
|
+
|
|
104
|
+
if offset > 0:
|
|
105
|
+
del self._buffer[:offset]
|
|
106
|
+
|
|
107
|
+
def send_data(self, data):
|
|
108
|
+
"""Send data to server"""
|
|
109
|
+
if self.socket:
|
|
110
|
+
try:
|
|
111
|
+
self.socket.sendall(data)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise ExceptionType.MessageHandlerError(e)
|
|
114
|
+
else:
|
|
115
|
+
raise ExceptionType.NotConnected("Not connected to server")
|
|
116
|
+
|
|
117
|
+
def handle_connection_lost(self):
|
|
118
|
+
"""Handle server disconnection"""
|
|
119
|
+
self._ping_task = None
|
|
120
|
+
|
|
121
|
+
self.client._connected = False
|
|
122
|
+
server_addr = self._server_addr if self._server_addr else ("unknown", 0)
|
|
123
|
+
|
|
124
|
+
for data_id, future in list(self.client.pending_responses.items()):
|
|
125
|
+
if hasattr(future, "set_exception") and not future.done():
|
|
126
|
+
future.set_exception(
|
|
127
|
+
ExceptionType.NotConnected(f"Server {server_addr} disconnected")
|
|
128
|
+
)
|
|
129
|
+
self.client.pending_responses.clear()
|
|
130
|
+
|
|
131
|
+
self.client.dispatcher.emit(
|
|
132
|
+
EventType.Client.DISCONNECT,
|
|
133
|
+
DisconnectData(server_addr=server_addr, transport=self.socket),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
self.socket.close()
|
|
137
|
+
|
|
138
|
+
self._connected = False
|
|
139
|
+
|
|
140
|
+
def keepalive_check(self):
|
|
141
|
+
"""Send periodic pings to server"""
|
|
142
|
+
while self.client._connected:
|
|
143
|
+
try:
|
|
144
|
+
ping_message = message_handler.create_ping()
|
|
145
|
+
self.send_data(ping_message)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
if not self.client._connected:
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
time.sleep(self.client.keepalive_interval)
|
|
153
|
+
|
|
154
|
+
self._missed_pings += 1
|
|
155
|
+
|
|
156
|
+
if self._missed_pings >= self.client.keepalive_max_missed:
|
|
157
|
+
self.client.dispatcher.emit(
|
|
158
|
+
EventType.Global.ERROR,
|
|
159
|
+
ErrorData(
|
|
160
|
+
error=ExceptionType.KeepaliveTimeout("Keepalive timeout"),
|
|
161
|
+
context="client.keepalive",
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
self.handle_connection_lost()
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TcpClient:
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
host: str = "127.0.0.1",
|
|
172
|
+
port: int = 8080,
|
|
173
|
+
compression_type: str = "zlib",
|
|
174
|
+
compression_level: int = 6,
|
|
175
|
+
compress: bool = True,
|
|
176
|
+
keepalive_interval: float = 30.0,
|
|
177
|
+
keepalive_max_missed: int = 3,
|
|
178
|
+
connection_timeout: float = 10.0,
|
|
179
|
+
flow_control: bool = True,
|
|
180
|
+
recv_buffer_size: int = 65536,
|
|
181
|
+
send_buffer_size: int = 65536,
|
|
182
|
+
):
|
|
183
|
+
self.host = host
|
|
184
|
+
self.port = port
|
|
185
|
+
self.dispatcher = EventDispatcher()
|
|
186
|
+
self._socket = None
|
|
187
|
+
self._protocol = None
|
|
188
|
+
self._connected = False
|
|
189
|
+
self.compression_type = compression_type
|
|
190
|
+
self.compression_level = compression_level
|
|
191
|
+
self.compress = compress
|
|
192
|
+
self.keepalive_interval = keepalive_interval
|
|
193
|
+
self.keepalive_max_missed = keepalive_max_missed
|
|
194
|
+
self.connection_timeout = connection_timeout
|
|
195
|
+
self.flow_control_enabled = flow_control
|
|
196
|
+
self.pending_responses = {}
|
|
197
|
+
self.seperator = b"\r\nSOCKETFLOW\r\n"
|
|
198
|
+
self.recv_buffer_size = recv_buffer_size
|
|
199
|
+
self.send_buffer_size = send_buffer_size
|
|
200
|
+
|
|
201
|
+
def connect(self):
|
|
202
|
+
"""Connect to server"""
|
|
203
|
+
try:
|
|
204
|
+
self._socket = socket_module.socket(
|
|
205
|
+
socket_module.AF_INET, socket_module.SOCK_STREAM
|
|
206
|
+
)
|
|
207
|
+
self._socket.settimeout(self.connection_timeout)
|
|
208
|
+
|
|
209
|
+
# Set socket buffer sizes
|
|
210
|
+
self._socket.setsockopt(
|
|
211
|
+
socket_module.SOL_SOCKET, socket_module.SO_RCVBUF, self.recv_buffer_size
|
|
212
|
+
)
|
|
213
|
+
self._socket.setsockopt(
|
|
214
|
+
socket_module.SOL_SOCKET, socket_module.SO_SNDBUF, self.send_buffer_size
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Enable TCP Keep-Alive
|
|
218
|
+
self._socket.setsockopt(
|
|
219
|
+
socket_module.SOL_SOCKET, socket_module.SO_KEEPALIVE, 1
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
self._socket.connect((self.host, self.port))
|
|
223
|
+
self._socket.settimeout(None)
|
|
224
|
+
|
|
225
|
+
self._protocol = TcpClientProtocol(self, self._socket)
|
|
226
|
+
self._connected = True
|
|
227
|
+
|
|
228
|
+
threading.Thread(target=self._receive_loop, daemon=True).start()
|
|
229
|
+
|
|
230
|
+
threading.Thread(target=self._protocol.keepalive_check, daemon=True).start()
|
|
231
|
+
|
|
232
|
+
server_addr = self._socket.getpeername()
|
|
233
|
+
self.dispatcher.emit(
|
|
234
|
+
EventType.Client.CONNECT,
|
|
235
|
+
ConnectData(server_addr=server_addr, transport=self._socket),
|
|
236
|
+
)
|
|
237
|
+
# Store server address for later use
|
|
238
|
+
self._protocol._server_addr = server_addr
|
|
239
|
+
|
|
240
|
+
except socket_module.timeout:
|
|
241
|
+
self.dispatcher.emit(
|
|
242
|
+
EventType.Global.ERROR,
|
|
243
|
+
ErrorData(
|
|
244
|
+
error=ExceptionType.ConnectionTimeout(
|
|
245
|
+
f"Connection timeout after {self.connection_timeout}s"
|
|
246
|
+
),
|
|
247
|
+
context="client.connect",
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
raise ExceptionType.ConnectionTimeout(
|
|
251
|
+
f"Connection timeout after {self.connection_timeout}s"
|
|
252
|
+
)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
self.dispatcher.emit(
|
|
255
|
+
EventType.Global.ERROR, ErrorData(error=e, context="client.connect")
|
|
256
|
+
)
|
|
257
|
+
raise
|
|
258
|
+
|
|
259
|
+
def _receive_loop(self):
|
|
260
|
+
"""Main receive loop"""
|
|
261
|
+
while self._connected:
|
|
262
|
+
try:
|
|
263
|
+
data = self._socket.recv(65536)
|
|
264
|
+
if not data:
|
|
265
|
+
break
|
|
266
|
+
self._protocol.handle_data(data)
|
|
267
|
+
except socket_module.timeout:
|
|
268
|
+
pass
|
|
269
|
+
except Exception:
|
|
270
|
+
break
|
|
271
|
+
self._protocol.handle_connection_lost()
|
|
272
|
+
|
|
273
|
+
def disconnect(self):
|
|
274
|
+
"""Disconnect from server"""
|
|
275
|
+
if self._connected:
|
|
276
|
+
self._socket.close()
|
|
277
|
+
self._connected = False
|
|
278
|
+
|
|
279
|
+
def send(
|
|
280
|
+
self,
|
|
281
|
+
data: Union[bytes, str],
|
|
282
|
+
data_id: Optional[str] = None,
|
|
283
|
+
path: Optional[str] = None,
|
|
284
|
+
wait_response: bool = False,
|
|
285
|
+
wait_response_timeout: Optional[float] = 30.0,
|
|
286
|
+
):
|
|
287
|
+
if not self._connected:
|
|
288
|
+
raise ExceptionType.NotConnected("Client is not connected")
|
|
289
|
+
|
|
290
|
+
if data_id is None:
|
|
291
|
+
data_id = str(uuid.uuid4())
|
|
292
|
+
|
|
293
|
+
headers = {
|
|
294
|
+
"type": "__user__",
|
|
295
|
+
"id": data_id,
|
|
296
|
+
"path": path,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if self.compress:
|
|
300
|
+
try:
|
|
301
|
+
compressed_msg = MultiCompressor.compress(
|
|
302
|
+
data, method=self.compression_type, level=self.compression_level
|
|
303
|
+
)
|
|
304
|
+
headers["compressed"] = True
|
|
305
|
+
length_bytes, encoded_message = message_manager.encode_with_length(
|
|
306
|
+
headers, compressed_msg
|
|
307
|
+
)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
self.dispatcher.emit(
|
|
310
|
+
EventType.Global.ERROR,
|
|
311
|
+
ErrorData(
|
|
312
|
+
error=ExceptionType.CompressionError(
|
|
313
|
+
f"Compression failed: {e}"
|
|
314
|
+
),
|
|
315
|
+
context="client.send",
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
raise ExceptionType.CompressionError(f"Compression failed: {e}")
|
|
319
|
+
else:
|
|
320
|
+
length_bytes, encoded_message = message_manager.encode_with_length(
|
|
321
|
+
headers, data
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
self._protocol.send_data(length_bytes + encoded_message)
|
|
325
|
+
|
|
326
|
+
if wait_response:
|
|
327
|
+
future = concurrent.futures.Future()
|
|
328
|
+
self.pending_responses[data_id] = future
|
|
329
|
+
|
|
330
|
+
def timeout_handler():
|
|
331
|
+
if data_id in self.pending_responses:
|
|
332
|
+
self.pending_responses.pop(data_id, None)
|
|
333
|
+
if not future.done():
|
|
334
|
+
future.set_exception(
|
|
335
|
+
ExceptionType.NoResponse(
|
|
336
|
+
f"No response received within {wait_response_timeout} timeout"
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
threading.Timer(wait_response_timeout, timeout_handler).start()
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
return future.result()
|
|
344
|
+
except ExceptionType.NotConnected:
|
|
345
|
+
raise ExceptionType.NoResponse(
|
|
346
|
+
f"No response received within {wait_response_timeout} timeout - not connected"
|
|
347
|
+
)
|
|
348
|
+
except ExceptionType.NoResponse:
|
|
349
|
+
raise ExceptionType.NoResponse(
|
|
350
|
+
f"No response received within {wait_response_timeout} timeout"
|
|
351
|
+
)
|
|
352
|
+
except Exception as e:
|
|
353
|
+
raise ExceptionType.ClientError(
|
|
354
|
+
f"Error while waiting for response: {e}"
|
|
355
|
+
)
|
|
356
|
+
finally:
|
|
357
|
+
self.pending_responses.pop(data_id, None)
|
|
358
|
+
|
|
359
|
+
def wait(self):
|
|
360
|
+
"""Wait for client to stay connected"""
|
|
361
|
+
try:
|
|
362
|
+
while self._connected:
|
|
363
|
+
time.sleep(0.1)
|
|
364
|
+
except KeyboardInterrupt:
|
|
365
|
+
self.disconnect()
|
|
366
|
+
|
|
367
|
+
def connect_and_wait(self):
|
|
368
|
+
"""Connect and wait"""
|
|
369
|
+
self.connect()
|
|
370
|
+
self.wait()
|
|
371
|
+
|
|
372
|
+
def is_connected(self):
|
|
373
|
+
return self._connected
|
|
374
|
+
|
|
375
|
+
def event(self, event_type: str):
|
|
376
|
+
return self.dispatcher.event(event_type)
|
|
377
|
+
|
|
378
|
+
def path(self, path: str, middleware=None):
|
|
379
|
+
return self.dispatcher.path(path, middleware)
|
|
380
|
+
|
|
381
|
+
def register_blueprint(self, blueprint):
|
|
382
|
+
blueprint._client = self # Associate blueprint with this client
|
|
383
|
+
self.dispatcher.register_blueprint(blueprint)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .event import EventType
|
|
2
|
+
from .dispatcher import EventDispatcher
|
|
3
|
+
from .compression import MultiCompressor
|
|
4
|
+
from .message_handler import MessageHandler
|
|
5
|
+
from .blueprint import Blueprint
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"EventType",
|
|
9
|
+
"EventDispatcher",
|
|
10
|
+
"MultiCompressor",
|
|
11
|
+
"MessageHandler",
|
|
12
|
+
"Blueprint",
|
|
13
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Dict, List, Callable, Optional
|
|
2
|
+
from .exceptions import ExceptionType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Blueprint:
|
|
6
|
+
def __init__(self, name: str):
|
|
7
|
+
self.name = name
|
|
8
|
+
self._event_handlers: Dict[str, List[Callable]] = {}
|
|
9
|
+
self._path_handlers: Dict[str, List[Callable]] = {}
|
|
10
|
+
self._path_middleware: Dict[str, List[Callable]] = {}
|
|
11
|
+
|
|
12
|
+
def event(self, event_type: str):
|
|
13
|
+
"""Decorator for event handlers"""
|
|
14
|
+
|
|
15
|
+
def decorator(func):
|
|
16
|
+
self.register_event(event_type, func)
|
|
17
|
+
return func
|
|
18
|
+
|
|
19
|
+
return decorator
|
|
20
|
+
|
|
21
|
+
def path(self, path: str, middleware=None):
|
|
22
|
+
"""Decorator for path handlers"""
|
|
23
|
+
|
|
24
|
+
def decorator(func):
|
|
25
|
+
self.register_path(path, func, middleware)
|
|
26
|
+
return func
|
|
27
|
+
|
|
28
|
+
return decorator
|
|
29
|
+
|
|
30
|
+
def register_event(self, event_type: str, handler: Callable):
|
|
31
|
+
"""Register an event handler"""
|
|
32
|
+
if event_type not in self._event_handlers:
|
|
33
|
+
self._event_handlers[event_type] = []
|
|
34
|
+
self._event_handlers[event_type].append(handler)
|
|
35
|
+
|
|
36
|
+
def register_path(self, path: str, handler: Callable, middleware=None):
|
|
37
|
+
"""Register a path handler"""
|
|
38
|
+
if path not in self._path_handlers:
|
|
39
|
+
self._path_handlers[path] = []
|
|
40
|
+
self._path_handlers[path].append(handler)
|
|
41
|
+
|
|
42
|
+
# Store middleware separately if provided
|
|
43
|
+
if middleware:
|
|
44
|
+
if path not in self._path_middleware:
|
|
45
|
+
self._path_middleware[path] = []
|
|
46
|
+
if isinstance(middleware, list):
|
|
47
|
+
self._path_middleware[path].extend(middleware)
|
|
48
|
+
else:
|
|
49
|
+
self._path_middleware[path].append(middleware)
|
|
50
|
+
|
|
51
|
+
def register_with_dispatcher(self, dispatcher):
|
|
52
|
+
"""Register all handlers with a dispatcher"""
|
|
53
|
+
for event_type, handlers in self._event_handlers.items():
|
|
54
|
+
for handler in handlers:
|
|
55
|
+
dispatcher.register_event(event_type, handler)
|
|
56
|
+
|
|
57
|
+
for path, handlers in self._path_handlers.items():
|
|
58
|
+
for handler in handlers:
|
|
59
|
+
# Get middleware for this path
|
|
60
|
+
middleware = self._path_middleware.get(path, [])
|
|
61
|
+
dispatcher.register_path(path, handler, middleware)
|
|
62
|
+
|
|
63
|
+
def is_connected(self, client_addr: tuple = None):
|
|
64
|
+
if hasattr(self, "_client") and self._client:
|
|
65
|
+
return self._client.is_connected()
|
|
66
|
+
elif hasattr(self, "_server") and self._server:
|
|
67
|
+
if not client_addr:
|
|
68
|
+
raise ExceptionType.BlueprintError("client_addr parameter is required")
|
|
69
|
+
return self._server.is_connected(client_addr)
|
|
70
|
+
else:
|
|
71
|
+
raise ExceptionType.BlueprintError(
|
|
72
|
+
"Blueprint not registered with client or server"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def send(
|
|
76
|
+
self,
|
|
77
|
+
data: bytes,
|
|
78
|
+
data_id: Optional[str] = None,
|
|
79
|
+
path: Optional[str] = None,
|
|
80
|
+
wait_response: bool = False,
|
|
81
|
+
wait_response_timeout: Optional[float] = 30.0,
|
|
82
|
+
):
|
|
83
|
+
"""Send message from client (for blueprints)"""
|
|
84
|
+
if hasattr(self, "_client") and self._client:
|
|
85
|
+
return self._client.send(
|
|
86
|
+
data, data_id, path, wait_response, wait_response_timeout
|
|
87
|
+
)
|
|
88
|
+
raise ExceptionType.BlueprintError("Blueprint not registered with client")
|
|
89
|
+
|
|
90
|
+
def send_client(
|
|
91
|
+
self,
|
|
92
|
+
client_addr: tuple,
|
|
93
|
+
data: bytes,
|
|
94
|
+
data_id: Optional[str] = None,
|
|
95
|
+
path: Optional[str] = None,
|
|
96
|
+
wait_response: bool = False,
|
|
97
|
+
wait_response_timeout: Optional[float] = 30.0,
|
|
98
|
+
):
|
|
99
|
+
"""Send message to client (for blueprints)"""
|
|
100
|
+
if hasattr(self, "_server") and self._server:
|
|
101
|
+
return self._server.send_client(
|
|
102
|
+
client_addr, data, data_id, path, wait_response, wait_response_timeout
|
|
103
|
+
)
|
|
104
|
+
raise ExceptionType.BlueprintError("Blueprint not registered with server")
|
|
105
|
+
|
|
106
|
+
def disconnect(self):
|
|
107
|
+
"""Disconnect client (for blueprints)"""
|
|
108
|
+
if hasattr(self, "_client") and self._client:
|
|
109
|
+
self._client.disconnect()
|
|
110
|
+
else:
|
|
111
|
+
raise ExceptionType.BlueprintError("Blueprint not registered with client")
|
|
112
|
+
|
|
113
|
+
def disconnect_client(self, client_addr: tuple):
|
|
114
|
+
"""Disconnect client (for blueprints)"""
|
|
115
|
+
if hasattr(self, "_server") and self._server:
|
|
116
|
+
self._server.disconnect_client(client_addr)
|
|
117
|
+
else:
|
|
118
|
+
raise ExceptionType.BlueprintError("Blueprint not registered with server")
|