wslink 2.1.3__py3-none-any.whl → 2.2.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.
- wslink/__init__.py +8 -1
- wslink/protocol.py +62 -46
- wslink/publish.py +3 -0
- wslink/websocket.py +38 -0
- {wslink-2.1.3.dist-info → wslink-2.2.0.dist-info}/METADATA +1 -1
- {wslink-2.1.3.dist-info → wslink-2.2.0.dist-info}/RECORD +8 -8
- {wslink-2.1.3.dist-info → wslink-2.2.0.dist-info}/WHEEL +0 -0
- {wslink-2.1.3.dist-info → wslink-2.2.0.dist-info}/top_level.txt +0 -0
wslink/__init__.py
CHANGED
@@ -54,7 +54,7 @@ def schedule_callback(delay, callback, *args, **kwargs):
|
|
54
54
|
return loop.call_later(delay, functools.partial(callback, *args, **kwargs))
|
55
55
|
|
56
56
|
|
57
|
-
def schedule_coroutine(delay, coro_func, *args, **kwargs):
|
57
|
+
def schedule_coroutine(delay, coro_func, *args, done_callback=None, **kwargs):
|
58
58
|
"""
|
59
59
|
Creates a coroutine out of the provided coroutine function coro_func and
|
60
60
|
the provided args and kwargs, then schedules the coroutine to be called
|
@@ -73,4 +73,11 @@ def schedule_coroutine(delay, coro_func, *args, **kwargs):
|
|
73
73
|
# See method above for comment on "get_event_loop()" vs "get_running_loop()".
|
74
74
|
loop = asyncio.get_event_loop()
|
75
75
|
coro_partial = functools.partial(coro_func, *args, **kwargs)
|
76
|
+
if done_callback is not None:
|
77
|
+
return loop.call_later(
|
78
|
+
delay,
|
79
|
+
lambda: asyncio.ensure_future(coro_partial()).add_done_callback(
|
80
|
+
done_callback
|
81
|
+
),
|
82
|
+
)
|
76
83
|
return loop.call_later(delay, lambda: asyncio.ensure_future(coro_partial()))
|
wslink/protocol.py
CHANGED
@@ -152,6 +152,7 @@ class WslinkHandler(object):
|
|
152
152
|
self.attachment_atomic = asyncio.Lock()
|
153
153
|
self.pub_manager = PublishManager()
|
154
154
|
self.unchunkers = {}
|
155
|
+
self.network_monitor = protocol.network_monitor
|
155
156
|
|
156
157
|
# Build the rpc method dictionary, assuming we were given a serverprotocol
|
157
158
|
if self.getServerProtocol():
|
@@ -257,7 +258,8 @@ class WslinkHandler(object):
|
|
257
258
|
|
258
259
|
full_message = self.unchunkers[client_id].process_chunk(msg.data)
|
259
260
|
if full_message is not None:
|
260
|
-
|
261
|
+
with self.network_monitor:
|
262
|
+
await self.onCompleteMessage(full_message, client_id)
|
261
263
|
|
262
264
|
async def onCompleteMessage(self, rpc, client_id):
|
263
265
|
logger.debug("wslink incoming msg %s", self.payloadWithSecretStripped(rpc))
|
@@ -282,23 +284,25 @@ class WslinkHandler(object):
|
|
282
284
|
|
283
285
|
# Prevent any further processing if token is not valid
|
284
286
|
if not self.isClientAuthenticated(client_id):
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
287
|
+
with self.network_monitor:
|
288
|
+
await self.sendWrappedError(
|
289
|
+
rpcid,
|
290
|
+
AUTHENTICATION_ERROR,
|
291
|
+
"Unauthorized: Skip message processing",
|
292
|
+
client_id=client_id,
|
293
|
+
)
|
291
294
|
return
|
292
295
|
|
293
296
|
# No matching method found
|
294
297
|
if not methodName in self.functionMap:
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
298
|
+
with self.network_monitor:
|
299
|
+
await self.sendWrappedError(
|
300
|
+
rpcid,
|
301
|
+
METHOD_NOT_FOUND,
|
302
|
+
"Unregistered method called",
|
303
|
+
methodName,
|
304
|
+
client_id=client_id,
|
305
|
+
)
|
302
306
|
return
|
303
307
|
|
304
308
|
obj, func = self.functionMap[methodName]
|
@@ -308,31 +312,34 @@ class WslinkHandler(object):
|
|
308
312
|
self.web_app.last_active_client_id = client_id
|
309
313
|
results = func(*args, **kwargs)
|
310
314
|
if inspect.isawaitable(results):
|
311
|
-
|
315
|
+
with self.network_monitor:
|
316
|
+
results = await results
|
312
317
|
|
313
318
|
if self.connections[client_id].closed:
|
314
319
|
# Connection was closed during RPC call.
|
315
320
|
return
|
316
321
|
|
317
|
-
|
318
|
-
|
319
|
-
|
322
|
+
with self.network_monitor:
|
323
|
+
await self.sendWrappedMessage(
|
324
|
+
rpcid, results, method=methodName, client_id=client_id
|
325
|
+
)
|
320
326
|
except Exception as e_inst:
|
321
327
|
captured_trace = traceback.format_exc()
|
322
328
|
logger.error("Exception raised")
|
323
329
|
logger.error(repr(e_inst))
|
324
330
|
logger.error(captured_trace)
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
331
|
+
with self.network_monitor:
|
332
|
+
await self.sendWrappedError(
|
333
|
+
rpcid,
|
334
|
+
EXCEPTION_ERROR,
|
335
|
+
"Exception raised",
|
336
|
+
{
|
337
|
+
"method": methodName,
|
338
|
+
"exception": repr(e_inst),
|
339
|
+
"trace": captured_trace,
|
340
|
+
},
|
341
|
+
client_id=client_id,
|
342
|
+
)
|
336
343
|
|
337
344
|
def payloadWithSecretStripped(self, payload):
|
338
345
|
payload = copy.deepcopy(payload)
|
@@ -397,13 +404,14 @@ class WslinkHandler(object):
|
|
397
404
|
except Exception:
|
398
405
|
# the content which is not serializable might be arbitrarily large, don't include.
|
399
406
|
# repr(content) would do that...
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
+
with self.network_monitor:
|
408
|
+
await self.sendWrappedError(
|
409
|
+
rpcid,
|
410
|
+
RESULT_SERIALIZE_ERROR,
|
411
|
+
"Method result cannot be serialized",
|
412
|
+
method,
|
413
|
+
client_id=client_id,
|
414
|
+
)
|
407
415
|
return
|
408
416
|
|
409
417
|
websockets = self.getAuthenticatedWebsockets(client_id, skip_last_active_client)
|
@@ -411,11 +419,15 @@ class WslinkHandler(object):
|
|
411
419
|
# aiohttp can not handle pending ws.send_bytes()
|
412
420
|
# tried with semaphore but got exception with >1
|
413
421
|
# https://github.com/aio-libs/aiohttp/issues/2934
|
414
|
-
|
415
|
-
|
416
|
-
for
|
417
|
-
|
418
|
-
|
422
|
+
with self.network_monitor:
|
423
|
+
async with self.attachment_atomic:
|
424
|
+
for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
|
425
|
+
for ws in websockets:
|
426
|
+
if ws is not None:
|
427
|
+
await ws.send_bytes(chunk)
|
428
|
+
|
429
|
+
# Network operation completed
|
430
|
+
self.network_monitor.network_call_completed()
|
419
431
|
|
420
432
|
async def sendWrappedError(self, rpcid, code, message, data=None, client_id=None):
|
421
433
|
wrapper = {
|
@@ -443,11 +455,15 @@ class WslinkHandler(object):
|
|
443
455
|
# aiohttp can not handle pending ws.send_bytes()
|
444
456
|
# tried with semaphore but got exception with >1
|
445
457
|
# https://github.com/aio-libs/aiohttp/issues/2934
|
446
|
-
|
447
|
-
|
448
|
-
for
|
449
|
-
|
450
|
-
|
458
|
+
with self.network_monitor:
|
459
|
+
async with self.attachment_atomic:
|
460
|
+
for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
|
461
|
+
for ws in websockets:
|
462
|
+
if ws is not None:
|
463
|
+
await ws.send_bytes(chunk)
|
464
|
+
|
465
|
+
# Network operation completed
|
466
|
+
self.network_monitor.network_call_completed()
|
451
467
|
|
452
468
|
def publish(self, topic, data, client_id=None, skip_last_active_client=False):
|
453
469
|
client_list = [client_id] if client_id else [c_id for c_id in self.connections]
|
wslink/publish.py
CHANGED
@@ -27,6 +27,7 @@ class PublishManager(object):
|
|
27
27
|
for protocol in self.protocols:
|
28
28
|
# The client is unknown - we send to any client who is subscribed to the topic
|
29
29
|
rpcid = "publish:{0}:{1}".format(topic, self.publishCount)
|
30
|
+
protocol.network_monitor.on_enter()
|
30
31
|
schedule_coroutine(
|
31
32
|
0,
|
32
33
|
protocol.sendWrappedMessage,
|
@@ -34,6 +35,8 @@ class PublishManager(object):
|
|
34
35
|
data,
|
35
36
|
client_id=client_id,
|
36
37
|
skip_last_active_client=skip_last_active_client,
|
38
|
+
# for schedule_coroutine call
|
39
|
+
done_callback=protocol.network_monitor.on_exit,
|
37
40
|
)
|
38
41
|
|
39
42
|
|
wslink/websocket.py
CHANGED
@@ -5,6 +5,7 @@ ServerProtocol to hook all the needed LinkProtocols together.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import logging
|
8
|
+
import asyncio
|
8
9
|
|
9
10
|
from . import register as exportRpc
|
10
11
|
from . import schedule_callback
|
@@ -65,6 +66,42 @@ class LinkProtocol(object):
|
|
65
66
|
# =============================================================================
|
66
67
|
|
67
68
|
|
69
|
+
class NetworkMonitor:
|
70
|
+
def __init__(self):
|
71
|
+
self.pending = 0
|
72
|
+
self.event = asyncio.Event()
|
73
|
+
|
74
|
+
def network_call_completed(self):
|
75
|
+
self.event.set()
|
76
|
+
|
77
|
+
def on_enter(self, *args, **kwargs):
|
78
|
+
self.pending += 1
|
79
|
+
|
80
|
+
def on_exit(self, *args, **kwargs):
|
81
|
+
self.pending -= 1
|
82
|
+
if self.pending == 0 and not self.event.is_set():
|
83
|
+
self.event.set()
|
84
|
+
|
85
|
+
# Sync ctx manager
|
86
|
+
def __enter__(self):
|
87
|
+
self.on_enter()
|
88
|
+
return self
|
89
|
+
|
90
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
91
|
+
self.on_exit()
|
92
|
+
|
93
|
+
# Async ctx manager
|
94
|
+
async def __aenter__(self):
|
95
|
+
self.on_enter()
|
96
|
+
return self
|
97
|
+
|
98
|
+
async def __aexit__(self, exc_t, exc_v, exc_tb):
|
99
|
+
self.on_exit()
|
100
|
+
while self.pending:
|
101
|
+
self.event.clear()
|
102
|
+
await self.event.wait()
|
103
|
+
|
104
|
+
|
68
105
|
class ServerProtocol(object):
|
69
106
|
"""
|
70
107
|
Defines the core server protocol for wslink. Gathers a list of LinkProtocol
|
@@ -72,6 +109,7 @@ class ServerProtocol(object):
|
|
72
109
|
"""
|
73
110
|
|
74
111
|
def __init__(self):
|
112
|
+
self.network_monitor = NetworkMonitor()
|
75
113
|
self.linkProtocols = []
|
76
114
|
self.secret = None
|
77
115
|
self.initialize()
|
@@ -1,14 +1,14 @@
|
|
1
1
|
wslink/LICENSE,sha256=I44UH7kDVqxDLnnlOWw_hFL2Fz7RjQ_4vPzZv9NYgTU,1483
|
2
|
-
wslink/__init__.py,sha256=
|
2
|
+
wslink/__init__.py,sha256=qdLGVhzpEXBoraTnLr5S-8bX9oK8pZCQWe8rZRlwAco,2946
|
3
3
|
wslink/chunking.py,sha256=1DJlGG6fjknGFrqPOtqUUc5tCrijldP7Kdx56d5e3Wg,7337
|
4
4
|
wslink/launcher.py,sha256=8VMs3juObLkyGYQFNLjMoo4qFpKIcxWz0kS-af-DKO4,21170
|
5
|
-
wslink/protocol.py,sha256=
|
6
|
-
wslink/publish.py,sha256=
|
5
|
+
wslink/protocol.py,sha256=T3F_Y_pZ4qXUApjpDoi9zXDkDvp3qnlKQICj2jnPlsA,16744
|
6
|
+
wslink/publish.py,sha256=Xyv9piZT4HxO5l_SA7zeReH8t6tisVHUgU57Hjzgkcg,1595
|
7
7
|
wslink/relay.py,sha256=E8Lzu2Ay7KbOheN1-ArAZawo8lLqdDgJXOZSBuMknYs,86
|
8
8
|
wslink/server.py,sha256=yvhCjpzPOfhbZrpDvW9i4H_uSyuQAe3ZOP-BRBmgHQA,9326
|
9
9
|
wslink/ssl_context.py,sha256=hNOJJCdrStws1Qf6vPvY4vTk9Bf8J5d90W3fS0cRv8o,2290
|
10
10
|
wslink/uri.py,sha256=woCQ4yChUqTMg9IT6YYDtUYeKmCg7OUCEgeBGA-19DY,384
|
11
|
-
wslink/websocket.py,sha256=
|
11
|
+
wslink/websocket.py,sha256=Gdp8ll5z3wq6gBU0GSVu2iq_o598Mxt8jQ1wZi6Z87A,5520
|
12
12
|
wslink/backends/__init__.py,sha256=cyJGjm-YyBSyOEX81owyTbJ3YnrA6dB7--B4LnsEtHI,1214
|
13
13
|
wslink/backends/aiohttp/__init__.py,sha256=Bc3uPVrhNXnn5Xt3Tck2Th-dsysz1VAnQdh8WzRn1y0,9253
|
14
14
|
wslink/backends/aiohttp/launcher.py,sha256=gHNMvtgNHEwBN_QBRDSCrTp2B4K1PsfV81rKaHi7Cxo,8897
|
@@ -19,7 +19,7 @@ wslink/backends/jupyter/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOy
|
|
19
19
|
wslink/backends/jupyter/core.py,sha256=PCQN-uZPFROnRv8B5dNwnwHV67o4Bpme3_Z6V-zbOUA,3864
|
20
20
|
wslink/backends/tornado/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOyNIxnGs,112
|
21
21
|
wslink/backends/tornado/core.py,sha256=tPMkkhWuO_ovkisVim0zcegwZKEAG4IRUdd_O_0a_R0,2157
|
22
|
-
wslink-2.
|
23
|
-
wslink-2.
|
24
|
-
wslink-2.
|
25
|
-
wslink-2.
|
22
|
+
wslink-2.2.0.dist-info/METADATA,sha256=SEj4XM87pZO5pMxU6WTxYxyGz7rZdxFOspQkHEYTkEg,3120
|
23
|
+
wslink-2.2.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
24
|
+
wslink-2.2.0.dist-info/top_level.txt,sha256=N0d8eqvhwhfW1p1yPTmvxlbzhjz7ZyhBfysNvaFqpQY,7
|
25
|
+
wslink-2.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|