@scrypted/server 0.6.24 → 0.7.2

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.

Potentially problematic release.


This version of @scrypted/server might be problematic. Click here for more details.

Files changed (51) hide show
  1. package/dist/http-interfaces.js +4 -1
  2. package/dist/http-interfaces.js.map +1 -1
  3. package/dist/listen-zero.js +5 -2
  4. package/dist/listen-zero.js.map +1 -1
  5. package/dist/plugin/media.js +25 -20
  6. package/dist/plugin/media.js.map +1 -1
  7. package/dist/plugin/plugin-console.js +157 -4
  8. package/dist/plugin/plugin-console.js.map +1 -1
  9. package/dist/plugin/plugin-device.js +2 -0
  10. package/dist/plugin/plugin-device.js.map +1 -1
  11. package/dist/plugin/plugin-host.js +5 -0
  12. package/dist/plugin/plugin-host.js.map +1 -1
  13. package/dist/plugin/plugin-remote-stats.js +30 -0
  14. package/dist/plugin/plugin-remote-stats.js.map +1 -0
  15. package/dist/plugin/plugin-remote-worker.js +69 -149
  16. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  17. package/dist/plugin/plugin-repl.js +4 -1
  18. package/dist/plugin/plugin-repl.js.map +1 -1
  19. package/dist/plugin/runtime/python-worker.js +1 -0
  20. package/dist/plugin/runtime/python-worker.js.map +1 -1
  21. package/dist/plugin/system.js +4 -0
  22. package/dist/plugin/system.js.map +1 -1
  23. package/dist/rpc.js +183 -45
  24. package/dist/rpc.js.map +1 -1
  25. package/dist/runtime.js +3 -0
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/threading.js +1 -0
  28. package/dist/threading.js.map +1 -1
  29. package/package.json +3 -4
  30. package/python/plugin_remote.py +134 -51
  31. package/python/rpc-iterator-test.py +45 -0
  32. package/python/rpc.py +168 -50
  33. package/python/rpc_reader.py +57 -60
  34. package/src/http-interfaces.ts +5 -1
  35. package/src/listen-zero.ts +6 -2
  36. package/src/plugin/media.ts +38 -35
  37. package/src/plugin/plugin-api.ts +4 -1
  38. package/src/plugin/plugin-console.ts +154 -6
  39. package/src/plugin/plugin-device.ts +3 -0
  40. package/src/plugin/plugin-host.ts +5 -0
  41. package/src/plugin/plugin-remote-stats.ts +36 -0
  42. package/src/plugin/plugin-remote-worker.ts +77 -178
  43. package/src/plugin/plugin-remote.ts +1 -1
  44. package/src/plugin/plugin-repl.ts +4 -1
  45. package/src/plugin/runtime/python-worker.ts +2 -0
  46. package/src/plugin/system.ts +6 -0
  47. package/src/rpc.ts +230 -52
  48. package/src/runtime.ts +3 -0
  49. package/src/threading.ts +2 -0
  50. package/test/rpc-iterator-test.ts +46 -0
  51. package/test/rpc-python-test.ts +44 -0
package/python/rpc.py CHANGED
@@ -4,7 +4,6 @@ import traceback
4
4
  import inspect
5
5
  from typing_extensions import TypedDict
6
6
  import weakref
7
- import sys
8
7
 
9
8
  jsonSerializable = set()
10
9
  jsonSerializable.add(float)
@@ -16,14 +15,16 @@ jsonSerializable.add(list)
16
15
 
17
16
 
18
17
  async def maybe_await(value):
19
- if (inspect.iscoroutinefunction(value) or inspect.iscoroutine(value)):
18
+ if (inspect.isawaitable(value)):
20
19
  return await value
21
20
  return value
22
21
 
23
22
 
24
- class RpcResultException(Exception):
25
- name = None
26
- stack = None
23
+ class RPCResultError(Exception):
24
+ name: str
25
+ stack: str
26
+ message: str
27
+ caught: Exception
27
28
 
28
29
  def __init__(self, caught, message):
29
30
  self.caught = caught
@@ -58,16 +59,31 @@ class RpcProxy(object):
58
59
  self.__dict__['__proxy_entry'] = entry
59
60
  self.__dict__['__proxy_constructor'] = proxyConstructorName
60
61
  self.__dict__['__proxy_peer'] = peer
61
- self.__dict__['__proxy_props'] = proxyProps
62
+ self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES] = proxyProps
62
63
  self.__dict__['__proxy_oneway_methods'] = proxyOneWayMethods
63
64
 
65
+ def __aiter__(self):
66
+ if self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES] and 'Symbol(Symbol.asyncIterator)' in self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES]:
67
+ return self
68
+ raise Exception('RpcProxy is not an async iterable')
69
+
70
+ async def __anext__(self):
71
+ if self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES] and 'Symbol(Symbol.asyncIterator)' in self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES]:
72
+ try:
73
+ return await RpcProxyMethod(self, self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES]['Symbol(Symbol.asyncIterator)']['next'])()
74
+ except RPCResultError as e:
75
+ if e.name == 'StopAsyncIteration':
76
+ raise StopAsyncIteration()
77
+ raise
78
+ raise Exception('RpcProxy is not an async iterable')
79
+
64
80
  def __getattr__(self, name):
65
81
  if name == '__proxy_finalizer_id':
66
82
  return self.dict['__proxy_entry']['finalizerId']
67
83
  if name in self.__dict__:
68
84
  return self.__dict__[name]
69
- if self.__dict__['__proxy_props'] and name in self.__dict__['__proxy_props']:
70
- return self.__dict__['__proxy_props'][name]
85
+ if self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES] and name in self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES]:
86
+ return self.__dict__[RpcPeer.PROPERTY_PROXY_PROPERTIES][name]
71
87
  return RpcProxyMethod(self, name)
72
88
 
73
89
  def __setattr__(self, name: str, value: Any) -> None:
@@ -85,6 +101,10 @@ class RpcProxy(object):
85
101
 
86
102
 
87
103
  class RpcPeer:
104
+ RPC_RESULT_ERROR_NAME = 'RPCResultError'
105
+ PROPERTY_PROXY_PROPERTIES = '__proxy_props'
106
+ PROPERTY_JSON_COPY_SERIALIZE_CHILDREN = '__json_copy_serialize_children'
107
+
88
108
  def __init__(self, send: Callable[[object, Callable[[Exception], None], Dict], None]) -> None:
89
109
  self.send = send
90
110
  self.idCounter = 1
@@ -97,12 +117,14 @@ class RpcPeer:
97
117
  self.pendingResults: Mapping[str, Future] = {}
98
118
  self.remoteWeakProxies: Mapping[str, any] = {}
99
119
  self.nameDeserializerMap: Mapping[str, RpcSerializer] = {}
120
+ self.onProxySerialization: Callable[[Any, str], Any] = None
121
+ self.killed = False
100
122
 
101
123
  def __apply__(self, proxyId: str, oneWayMethods: List[str], method: str, args: list):
102
124
  serializationContext: Dict = {}
103
125
  serializedArgs = []
104
126
  for arg in args:
105
- serializedArgs.append(self.serialize(arg, False, serializationContext))
127
+ serializedArgs.append(self.serialize(arg, serializationContext))
106
128
 
107
129
  rpcApply = {
108
130
  'type': 'apply',
@@ -124,21 +146,112 @@ class RpcPeer:
124
146
  self.send(rpcApply, reject, serializationContext)
125
147
  return self.createPendingResult(send)
126
148
 
127
- def kill(self):
149
+ def kill(self, message: str = None):
150
+ # not thread safe..
151
+ if self.killed:
152
+ return
128
153
  self.killed = True
129
154
 
130
- def createErrorResult(self, result: Any, name: str, message: str, tb: str):
131
- result['stack'] = tb if tb else 'no stack'
132
- result['result'] = name if name else 'no name'
133
- result['message'] = message if message else 'no message'
155
+ error = RPCResultError(None, message or 'peer was killed')
156
+ # this.killedDeferred.reject(error);
157
+ for str, future in self.pendingResults.items():
158
+ future.set_exception(error)
159
+
160
+ self.pendingResults = None
161
+ self.params = None
162
+ self.remoteWeakProxies = None
163
+ self.localProxyMap = None
164
+ self.localProxied = None
165
+
166
+ def createErrorResult(self, result: Any, e: Exception):
167
+ s = self.serializeError(e)
168
+ result['result'] = s
169
+ result['throw'] = True
170
+
171
+ # TODO 3/2/2023 deprecate these properties
172
+ tb = traceback.format_exc()
173
+ message = str(e)
174
+ result['stack'] = tb or '[no stack]',
175
+ result['message'] = message or '[no message]',
176
+ # END TODO
177
+
178
+
179
+ def deserializeError(e: Dict) -> RPCResultError:
180
+ error = RPCResultError(None, e.get('message'))
181
+ error.stack = e.get('stack')
182
+ error.name = e.get('name')
183
+ return error
184
+
185
+ def serializeError(self, e: Exception):
186
+ tb = traceback.format_exc()
187
+ name = type(e).__name__
188
+ message = str(e)
189
+
190
+ serialized = {
191
+ 'stack': tb or '[no stack]',
192
+ 'name': name or '[no name]',
193
+ 'message': message or '[no message]',
194
+ }
195
+
196
+ return {
197
+ '__remote_constructor_name': RpcPeer.RPC_RESULT_ERROR_NAME,
198
+ '__serialized_value': serialized,
199
+ }
200
+
201
+ # def getProxyProperties(value):
202
+ # return getattr(value, RpcPeer.PROPERTY_PROXY_PROPERTIES, None)
203
+
204
+ # def setProxyProperties(value, properties):
205
+ # setattr(value, RpcPeer.PROPERTY_PROXY_PROPERTIES, properties)
206
+
207
+ def prepareProxyProperties(value):
208
+ if not hasattr(value, '__aiter__') or not hasattr(value, '__anext__'):
209
+ return getattr(value, RpcPeer.PROPERTY_PROXY_PROPERTIES, None)
210
+
211
+ props = getattr(value, RpcPeer.PROPERTY_PROXY_PROPERTIES, None) or {}
212
+ if not props.get('Symbol(Symbol.asyncIterator)'):
213
+ props['Symbol(Symbol.asyncIterator)'] = {
214
+ 'next': '__anext__',
215
+ 'throw': 'athrow',
216
+ 'return': 'aclose',
217
+ }
218
+ return props
219
+
220
+ def isTransportSafe(value: any):
221
+ return not value or (type(value) in jsonSerializable)
222
+
223
+ def serialize(self, value, serializationContext: Dict):
224
+ if type(value) == dict and value.get(RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN, None):
225
+ ret = {}
226
+ for (key, val) in value.items():
227
+ ret[key] = self.serialize(val, serializationContext)
228
+ return ret
134
229
 
135
- def serialize(self, value, requireProxy, serializationContext: Dict):
136
- if (not value or (not requireProxy and type(value) in jsonSerializable)):
230
+ if (RpcPeer.isTransportSafe(value)):
137
231
  return value
138
232
 
139
233
  __remote_constructor_name = 'Function' if callable(value) else value.__proxy_constructor if hasattr(
140
234
  value, '__proxy_constructor') else type(value).__name__
141
235
 
236
+ if isinstance(value, Exception):
237
+ return self.serializeError(value)
238
+
239
+ serializerMapName = self.constructorSerializerMap.get(
240
+ type(value), None)
241
+ if serializerMapName:
242
+ __remote_constructor_name = serializerMapName
243
+ serializer = self.nameDeserializerMap.get(serializerMapName, None)
244
+ serialized = serializer.serialize(value, serializationContext)
245
+ ret = {
246
+ '__remote_proxy_id': None,
247
+ '__remote_proxy_finalizer_id': None,
248
+ '__remote_constructor_name': __remote_constructor_name,
249
+ '__remote_proxy_props': RpcPeer.prepareProxyProperties(value),
250
+ '__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
251
+ '__serialized_value': serialized,
252
+ }
253
+ return ret
254
+
142
255
  proxiedEntry = self.localProxied.get(value, None)
143
256
  if proxiedEntry:
144
257
  proxiedEntry['finalizerId'] = str(self.proxyCounter)
@@ -147,7 +260,7 @@ class RpcPeer:
147
260
  '__remote_proxy_id': proxiedEntry['id'],
148
261
  '__remote_proxy_finalizer_id': proxiedEntry['finalizerId'],
149
262
  '__remote_constructor_name': __remote_constructor_name,
150
- '__remote_proxy_props': getattr(value, '__proxy_props', None),
263
+ '__remote_proxy_props': RpcPeer.prepareProxyProperties(value),
151
264
  '__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
152
265
  }
153
266
  return ret
@@ -160,22 +273,6 @@ class RpcPeer:
160
273
  }
161
274
  return ret
162
275
 
163
- serializerMapName = self.constructorSerializerMap.get(
164
- type(value), None)
165
- if serializerMapName:
166
- __remote_constructor_name = serializerMapName
167
- serializer = self.nameDeserializerMap.get(serializerMapName, None)
168
- serialized = serializer.serialize(value, serializationContext)
169
- ret = {
170
- '__remote_proxy_id': None,
171
- '__remote_proxy_finalizer_id': None,
172
- '__remote_constructor_name': __remote_constructor_name,
173
- '__remote_proxy_props': getattr(value, '__proxy_props', None),
174
- '__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
175
- '__serialized_value': serialized,
176
- }
177
- return ret
178
-
179
276
  proxyId = str(self.proxyCounter)
180
277
  self.proxyCounter = self.proxyCounter + 1
181
278
  proxiedEntry = {
@@ -185,17 +282,24 @@ class RpcPeer:
185
282
  self.localProxied[value] = proxiedEntry
186
283
  self.localProxyMap[proxyId] = value
187
284
 
285
+ if self.onProxySerialization:
286
+ __remote_proxy_props = self.onProxySerialization(value, proxyId)
287
+ else:
288
+ __remote_proxy_props = RpcPeer.prepareProxyProperties(value)
289
+
188
290
  ret = {
189
291
  '__remote_proxy_id': proxyId,
190
292
  '__remote_proxy_finalizer_id': proxyId,
191
293
  '__remote_constructor_name': __remote_constructor_name,
192
- '__remote_proxy_props': getattr(value, '__proxy_props', None),
294
+ '__remote_proxy_props': __remote_proxy_props,
193
295
  '__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
194
296
  }
195
297
 
196
298
  return ret
197
299
 
198
300
  def finalize(self, localProxiedEntry: LocalProxiedEntry):
301
+ if self.killed:
302
+ return
199
303
  id = localProxiedEntry['id']
200
304
  self.remoteWeakProxies.pop(id, None)
201
305
  rpcFinalize = {
@@ -224,6 +328,13 @@ class RpcPeer:
224
328
  if type(value) != dict:
225
329
  return value
226
330
 
331
+ copySerializeChildren = value.get(RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN, None)
332
+ if copySerializeChildren:
333
+ ret = {}
334
+ for (key, val) in value.items():
335
+ ret[key] = self.deserialize(val, deserializationContext)
336
+ return ret
337
+
227
338
  __remote_proxy_id = value.get('__remote_proxy_id', None)
228
339
  __remote_proxy_finalizer_id = value.get(
229
340
  '__remote_proxy_finalizer_id', None)
@@ -235,6 +346,9 @@ class RpcPeer:
235
346
  __remote_proxy_oneway_methods = value.get(
236
347
  '__remote_proxy_oneway_methods', None)
237
348
 
349
+ if __remote_constructor_name == RpcPeer.RPC_RESULT_ERROR_NAME:
350
+ return RpcPeer.deserializeError(__serialized_value)
351
+
238
352
  if __remote_proxy_id:
239
353
  weakref = self.remoteWeakProxies.get('__remote_proxy_id', None)
240
354
  proxy = weakref() if weakref else None
@@ -247,7 +361,7 @@ class RpcPeer:
247
361
  if __local_proxy_id:
248
362
  ret = self.localProxyMap.get(__local_proxy_id, None)
249
363
  if not ret:
250
- raise RpcResultException(
364
+ raise RPCResultError(
251
365
  None, 'invalid local proxy id %s' % __local_proxy_id)
252
366
  return ret
253
367
 
@@ -258,7 +372,7 @@ class RpcPeer:
258
372
 
259
373
  return value
260
374
 
261
- async def handleMessage(self, message: Any, deserializationContext: Dict):
375
+ async def handleMessage(self, message: Dict, deserializationContext: Dict):
262
376
  try:
263
377
  messageType = message['type']
264
378
  if messageType == 'param':
@@ -271,8 +385,7 @@ class RpcPeer:
271
385
  try:
272
386
  value = self.params.get(message['param'], None)
273
387
  value = await maybe_await(value)
274
- result['result'] = self.serialize(
275
- value, message.get('requireProxy', None), serializationContext)
388
+ result['result'] = self.serialize(value, serializationContext)
276
389
  except Exception as e:
277
390
  tb = traceback.format_exc()
278
391
  self.createErrorResult(
@@ -299,6 +412,9 @@ class RpcPeer:
299
412
  for arg in (message['args'] or []):
300
413
  args.append(self.deserialize(arg, deserializationContext))
301
414
 
415
+ # if method == 'asend' and hasattr(target, '__aiter__') and hasattr(target, '__anext__') and not len(args):
416
+ # args.append(None)
417
+
302
418
  value = None
303
419
  if method:
304
420
  if not hasattr(target, method):
@@ -309,12 +425,11 @@ class RpcPeer:
309
425
  else:
310
426
  value = await maybe_await(target(*args))
311
427
 
312
- result['result'] = self.serialize(value, False, serializationContext)
428
+ result['result'] = self.serialize(value, serializationContext)
429
+ except StopAsyncIteration as e:
430
+ self.createErrorResult(result, e)
313
431
  except Exception as e:
314
- tb = traceback.format_exc()
315
- # print('failure', method, e, tb)
316
- self.createErrorResult(
317
- result, type(e).__name__, str(e), tb)
432
+ self.createErrorResult(result, e)
318
433
 
319
434
  if not message.get('oneway', False):
320
435
  self.send(result, None, serializationContext)
@@ -323,18 +438,21 @@ class RpcPeer:
323
438
  id = message['id']
324
439
  future = self.pendingResults.get(id, None)
325
440
  if not future:
326
- raise RpcResultException(
441
+ raise RPCResultError(
327
442
  None, 'unknown result %s' % id)
328
443
  del self.pendingResults[id]
329
- if hasattr(message, 'message') or hasattr(message, 'stack'):
330
- e = RpcResultException(
444
+ if (hasattr(message, 'message') or hasattr(message, 'stack')) and not hasattr(message, 'throw'):
445
+ e = RPCResultError(
331
446
  None, message.get('message', None))
332
447
  e.stack = message.get('stack', None)
333
448
  e.name = message.get('name', None)
334
449
  future.set_exception(e)
335
450
  return
336
- future.set_result(self.deserialize(
337
- message.get('result', None), deserializationContext))
451
+ deserialized = self.deserialize(message.get('result', None), deserializationContext)
452
+ if message.get('throw'):
453
+ future.set_exception(deserialized)
454
+ else:
455
+ future.set_result(deserialized)
338
456
  elif messageType == 'finalize':
339
457
  finalizerId = message.get('__local_proxy_finalizer_id', None)
340
458
  proxyId = message['__local_proxy_id']
@@ -347,7 +465,7 @@ class RpcPeer:
347
465
  self.localProxied.pop(local, None)
348
466
  local = self.localProxyMap.pop(proxyId, None)
349
467
  else:
350
- raise RpcResultException(
468
+ raise RPCResultError(
351
469
  None, 'unknown rpc message type %s' % type)
352
470
  except Exception as e:
353
471
  print("unhandled rpc error", self.peerName, e)
@@ -361,7 +479,7 @@ class RpcPeer:
361
479
  self.idCounter = self.idCounter + 1
362
480
  future = Future()
363
481
  self.pendingResults[id] = future
364
- await cb(id, lambda e: future.set_exception(RpcResultException(e, None)))
482
+ await cb(id, lambda e: future.set_exception(RPCResultError(e, None)))
365
483
  return await future
366
484
 
367
485
  async def getParam(self, param):
@@ -2,33 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import base64
5
- import gc
6
5
  import json
7
- import sys
8
6
  import os
9
- import platform
10
- import shutil
11
- import subprocess
7
+ import sys
12
8
  import threading
13
- import time
14
- import traceback
15
- import zipfile
16
9
  from asyncio.events import AbstractEventLoop
17
- from asyncio.futures import Future
18
- from asyncio.streams import StreamReader, StreamWriter
19
- from collections.abc import Mapping
20
- from io import StringIO
21
10
  from os import sys
22
- from typing import Any, List, Optional, Set, Tuple
11
+ from typing import List
23
12
 
24
13
  import aiofiles
25
- import scrypted_python.scrypted_sdk.types
26
- from scrypted_python.scrypted_sdk import ScryptedStatic, PluginFork
27
- from scrypted_python.scrypted_sdk.types import Device, DeviceManifest, EventDetails, ScryptedInterfaceProperty, Storage
28
- from typing_extensions import TypedDict
29
14
  import rpc
30
- import multiprocessing
31
- import multiprocessing.connection
32
15
 
33
16
 
34
17
  class BufferSerializer(rpc.RpcSerializer):
@@ -53,39 +36,61 @@ class SidebandBufferSerializer(rpc.RpcSerializer):
53
36
  buffer = buffers.pop()
54
37
  return buffer
55
38
 
56
- async def readLoop(loop, peer: rpc.RpcPeer, reader):
39
+ async def readLoop(loop, peer: rpc.RpcPeer, reader: asyncio.StreamReader):
57
40
  deserializationContext = {
58
41
  'buffers': []
59
42
  }
60
43
 
44
+ if isinstance(reader, asyncio.StreamReader):
45
+ async def read(n):
46
+ return await reader.readexactly(n)
47
+ else:
48
+ async def read(n):
49
+ return await reader.read(n)
50
+
51
+
61
52
  while True:
62
- try:
63
- lengthBytes = await reader.read(4)
64
- typeBytes = await reader.read(1)
65
- type = typeBytes[0]
66
- length = int.from_bytes(lengthBytes, 'big')
67
- data = await reader.read(length - 1)
68
-
69
- if type == 1:
70
- deserializationContext['buffers'].append(data)
71
- continue
72
-
73
- message = json.loads(data)
74
- asyncio.run_coroutine_threadsafe(
75
- peer.handleMessage(message, deserializationContext), loop)
76
-
77
- deserializationContext = {
78
- 'buffers': []
79
- }
80
- except Exception as e:
81
- print('read loop error: ' + peer.peerName, e)
82
- sys.exit()
83
-
84
- async def prepare_peer_readloop(loop: AbstractEventLoop, readFd: int, writeFd: int):
85
- reader = await aiofiles.open(readFd, mode='rb')
53
+ lengthBytes = await read(4)
54
+ typeBytes = await read(1)
55
+ type = typeBytes[0]
56
+ length = int.from_bytes(lengthBytes, 'big')
57
+ data = await read(length - 1)
58
+
59
+ if type == 1:
60
+ deserializationContext['buffers'].append(data)
61
+ continue
62
+
63
+ message = json.loads(data)
64
+ asyncio.run_coroutine_threadsafe(
65
+ peer.handleMessage(message, deserializationContext), loop)
66
+
67
+ deserializationContext = {
68
+ 'buffers': []
69
+ }
70
+
71
+ async def prepare_peer_readloop(loop: AbstractEventLoop, readFd: int = None, writeFd: int = None, reader: asyncio.StreamReader = None, writer: asyncio.StreamWriter = None):
72
+ reader = reader or await aiofiles.open(readFd, mode='rb')
86
73
 
87
74
  mutex = threading.Lock()
88
75
 
76
+ if writer:
77
+ def write(buffers, reject):
78
+ try:
79
+ for b in buffers:
80
+ writer.write(b)
81
+ except Exception as e:
82
+ if reject:
83
+ reject(e)
84
+ return None
85
+ else:
86
+ def write(buffers, reject):
87
+ try:
88
+ for b in buffers:
89
+ os.write(writeFd, b)
90
+ except Exception as e:
91
+ if reject:
92
+ reject(e)
93
+
89
94
  def send(message, reject=None, serializationContext=None):
90
95
  with mutex:
91
96
  if serializationContext:
@@ -95,34 +100,26 @@ async def prepare_peer_readloop(loop: AbstractEventLoop, readFd: int, writeFd: i
95
100
  length = len(buffer) + 1
96
101
  lb = length.to_bytes(4, 'big')
97
102
  type = 1
98
- try:
99
- os.write(writeFd, lb)
100
- os.write(writeFd, bytes([type]))
101
- os.write(writeFd, buffer)
102
- except Exception as e:
103
- if reject:
104
- reject(e)
105
- return
103
+ write([lb, bytes([type]), buffer], reject)
106
104
 
107
105
  jsonString = json.dumps(message)
108
106
  b = bytes(jsonString, 'utf8')
109
107
  length = len(b) + 1
110
108
  lb = length.to_bytes(4, 'big')
111
109
  type = 0
112
- try:
113
- os.write(writeFd, lb)
114
- os.write(writeFd, bytes([type]))
115
- os.write(writeFd, b)
116
- except Exception as e:
117
- if reject:
118
- reject(e)
110
+ write([lb, bytes([type]), b], reject)
119
111
 
120
112
  peer = rpc.RpcPeer(send)
121
113
  peer.nameDeserializerMap['Buffer'] = SidebandBufferSerializer()
122
114
  peer.constructorSerializerMap[bytes] = 'Buffer'
123
115
  peer.constructorSerializerMap[bytearray] = 'Buffer'
116
+ peer.constructorSerializerMap[memoryview] = 'Buffer'
124
117
 
125
118
  async def peerReadLoop():
126
- await readLoop(loop, peer, reader)
119
+ try:
120
+ await readLoop(loop, peer, reader)
121
+ except:
122
+ peer.kill()
123
+ raise
127
124
 
128
125
  return peer, peerReadLoop
@@ -61,7 +61,11 @@ export function createResponseInterface(res: Response, unzippedDir: string, file
61
61
  }
62
62
  }
63
63
  }
64
- res.sendFile(filePath);
64
+
65
+ // prefer etag
66
+ res.sendFile(filePath, {
67
+ cacheControl: false,
68
+ });
65
69
  }
66
70
 
67
71
  sendSocket(socket: net.Socket, options: HttpResponseOptions) {
@@ -26,10 +26,14 @@ export async function listenZeroSingleClient(hostname?: string) {
26
26
 
27
27
  clientPromise.catch(() => { });
28
28
 
29
+ let host = hostname;
30
+ if (!host || host === '0.0.0.0')
31
+ host = '127.0.0.1';
32
+
29
33
  return {
30
34
  server,
31
- url: `tcp://127.0.0.1:${port}`,
32
- host: '127.0.0.1',
35
+ url: `tcp://${host}:${port}`,
36
+ host,
33
37
  port,
34
38
  clientPromise,
35
39
  }