GNServer 0.0.0.0.13__tar.gz → 0.0.0.0.15__tar.gz

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.
@@ -37,3 +37,4 @@ from KeyisBClient.gn import GNRequest, GNResponse, CORSObject, FileObject, Templ
37
37
 
38
38
 
39
39
 
40
+ from ._client import AsyncClient
@@ -383,6 +383,8 @@ class App:
383
383
  self._cors: Optional[gn.CORSObject] = None
384
384
 
385
385
  def route(self, method: str, path: str, cors: Optional[gn.CORSObject] = None):
386
+ if path == '/':
387
+ path = ''
386
388
  def decorator(fn: Callable[..., Any]):
387
389
  regex, param_types = _compile_path(path)
388
390
  self._routes.append(
@@ -547,19 +549,19 @@ class App:
547
549
  # получаем длинну пакета
548
550
  mode, stream, lenght = gn.GNRequest.type(buf)
549
551
 
550
- if mode != 2: # не наш пакет
551
- logger.debug(f'Пакет отклонен: mode пакета {mode}. Разрешен 2')
552
+ if mode not in (1, 2): # не наш пакет
553
+ logger.debug(f'Пакет отклонен: mode пакета {mode}. Разрешен 1, 2')
552
554
  return
553
555
 
554
556
  if not stream: # если не стрим, то ждем конец quic стрима и запускаем обработку ответа
555
557
  if event.end_stream:
556
- request = gn.GNRequest.deserialize(buf, 1)
558
+ request = gn.GNRequest.deserialize(buf, mode)
557
559
  # request.stream_id = event.stream_id
558
560
  # loop = asyncio.get_event_loop()
559
561
  # request.fut = loop.create_future()
560
562
 
561
563
  request.stream_id = event.stream_id
562
- asyncio.create_task(self._handle_request(request))
564
+ asyncio.create_task(self._handle_request(request, mode))
563
565
  logger.debug(f'Отправлена задача разрешения пакета {request} route -> {request.route}')
564
566
 
565
567
  self._buffer.pop(event.stream_id, None)
@@ -578,7 +580,7 @@ class App:
578
580
  del buf[:lenght]
579
581
 
580
582
  # формируем запрос
581
- request = gn.GNRequest.deserialize(data, 1)
583
+ request = gn.GNRequest.deserialize(data, mode)
582
584
 
583
585
  logger.debug(request, f'event.stream_id -> {event.stream_id}')
584
586
 
@@ -615,9 +617,9 @@ class App:
615
617
  yield chunk
616
618
 
617
619
  request._stream = w
618
- asyncio.create_task(self._handle_request(request))
620
+ asyncio.create_task(self._handle_request(request, mode))
619
621
 
620
- async def _handle_request(self, request: gn.GNRequest):
622
+ async def _handle_request(self, request: gn.GNRequest, mode: int):
621
623
  try:
622
624
 
623
625
  response = await self._api.dispatch(request)
@@ -626,26 +628,26 @@ class App:
626
628
  async for chunk in response: # type: ignore[misc]
627
629
  chunk._stream = True
628
630
  chunk = await self.resolve_response(chunk)
629
- self._quic.send_stream_data(request.stream_id, chunk.serialize(1), end_stream=False)
631
+ self._quic.send_stream_data(request.stream_id, chunk.serialize(mode), end_stream=False)
630
632
  self.transmit()
631
633
 
632
634
  l = gn.GNResponse('gn:end-stream')
633
635
  l._stream = True
634
636
  l = self.resolve_response(l)
635
- self._quic.send_stream_data(request.stream_id, l.serialize(1), end_stream=True)
637
+ self._quic.send_stream_data(request.stream_id, l.serialize(mode), end_stream=True)
636
638
  self.transmit()
637
639
  return
638
640
 
639
641
 
640
642
  response = await self.resolve_response(response)
641
- self._quic.send_stream_data(request.stream_id, response.serialize(1), end_stream=True)
643
+ self._quic.send_stream_data(request.stream_id, response.serialize(mode), end_stream=True)
642
644
  logger.debug(f'Отправлен на сервер ответ -> {response.command} {response.payload if response.payload and len((response.payload)) < 200 else ''}')
643
645
  self.transmit()
644
646
  except Exception as e:
645
647
  logger.error('GNServer: error\n' + traceback.format_exc())
646
648
 
647
649
  response = gn.GNResponse('gn:backend:500')
648
- self._quic.send_stream_data(request.stream_id, response.serialize(1), end_stream=True)
650
+ self._quic.send_stream_data(request.stream_id, response.serialize(mode), end_stream=True)
649
651
  self.transmit()
650
652
 
651
653
  async def resolve_response(self, response: gn.GNResponse) -> gn.GNResponse:
@@ -0,0 +1,608 @@
1
+
2
+ import os
3
+ import httpx
4
+ import asyncio
5
+ import typing as _typing
6
+ import logging as logging2
7
+ from typing import Union, List, Dict, Tuple, Optional
8
+ import datetime
9
+ logging2.basicConfig(level=logging2.INFO)
10
+
11
+ from KeyisBLogging import logging
12
+ from typing import Dict, List, Tuple, Optional, cast, AsyncGenerator, Callable
13
+ from itertools import count
14
+ from aioquic.asyncio.client import connect
15
+ from aioquic.asyncio.protocol import QuicConnectionProtocol
16
+ from aioquic.h3.connection import H3_ALPN, H3Connection
17
+ from aioquic.h3.events import DataReceived, DatagramReceived, H3Event, HeadersReceived
18
+ from aioquic.quic.configuration import QuicConfiguration
19
+ from aioquic.quic.events import QuicEvent
20
+
21
+
22
+ import time
23
+ import json, ssl, asyncio, struct, base64, hashlib
24
+ from typing import Any, Dict, Optional
25
+ import websockets
26
+
27
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
28
+ import os
29
+ import msgpack
30
+ import logging
31
+ from httpx import Request, Headers, URL
32
+ logging.basicConfig(level=logging.DEBUG)
33
+ logging.getLogger("websockets").setLevel(logging.DEBUG)
34
+
35
+ import KeyisBClient
36
+ from KeyisBClient import Url
37
+ httpxAsyncClient = httpx.AsyncClient(verify=KeyisBClient.ssl_gw_crt_path, timeout=200)
38
+
39
+ class GNExceptions:
40
+ class ConnectionError:
41
+ class openconnector():
42
+ """Ошибка подключения к серверу openconnector.gn"""
43
+
44
+ class connection(Exception):
45
+ def __init__(self, message="Ошибка подключения к серверу openconnector.gn. Сервер не найден."):
46
+ super().__init__(message)
47
+
48
+ class timeout(Exception):
49
+ def __init__(self, message="Ошибка подключения к серверу openconnector.gn. Проблема с сетью или сервер перегружен."):
50
+ super().__init__(message)
51
+
52
+ class data(Exception):
53
+ def __init__(self, message="Ошибка подключения к серверу openconnector.gn. Сервер не подтвердил подключение."):
54
+ super().__init__(message)
55
+
56
+ class dns_core():
57
+ """Ошибка подключения к серверу dns.core"""
58
+ class connection(Exception):
59
+ def __init__(self, message="Ошибка подключения к серверу dns.core Сервер не найден."):
60
+ super().__init__(message)
61
+
62
+ class timeout(Exception):
63
+ def __init__(self, message="Ошибка подключения к серверу dns.core Проблема с сетью или сервер перегружен"):
64
+ super().__init__(message)
65
+
66
+ class data(Exception):
67
+ def __init__(self, message="Ошибка подключения к серверу dns.core Сервер не подтвердил подключение."):
68
+ super().__init__(message)
69
+
70
+
71
+
72
+ class connector():
73
+ """Ошибка подключения к серверу <?>~connector.gn"""
74
+
75
+ class connection(Exception):
76
+ def __init__(self, message="Ошибка подключения к серверу <?>~connector.gn. Сервер не найден."):
77
+ super().__init__(message)
78
+
79
+ class timeout(Exception):
80
+ def __init__(self, message="Ошибка подключения к серверу <?>~connector.gn. Проблема с сетью или сервер перегружен"):
81
+ super().__init__(message)
82
+
83
+ class data(Exception):
84
+ def __init__(self, message="Ошибка подключения к серверу <?>~connector.gn. Сервер не подтвердил подключение."):
85
+ super().__init__(message)
86
+
87
+
88
+
89
+ from KeyisBClient.gn import GNRequest, GNResponse, GNProtocol
90
+
91
+
92
+
93
+
94
+ class AsyncClient:
95
+ def __init__(self):
96
+ self.__dns_core__ipv4 = '51.250.85.38:52943'
97
+ self.__dns_gn__ipv4 = None
98
+
99
+ self.__user = {}
100
+ self.__current_session = {}
101
+ self.__request_callbacks = {}
102
+ self.__response_callbacks = {}
103
+
104
+ self._client: QuicClient = QuicClient()
105
+
106
+ self._active_connections: Dict[str, Any] = {}
107
+
108
+ async def _getCoreDNS(self, domain: str):
109
+
110
+ if domain.split('.')[-1].isdigit() and domain.split(':')[-1].isdigit():
111
+ r2_data = {
112
+ "ip": domain.split(':')[0],
113
+ "port": int(domain.split(':')[-1])
114
+ }
115
+ return r2_data
116
+
117
+ try:
118
+ if self.__dns_gn__ipv4 is None:
119
+ r1 = await httpxAsyncClient.request('GET', f'https://{self.__dns_core__ipv4}/gn/getIp?d=dns.gn')
120
+ if r1.status_code != 200:
121
+ raise GNExceptions.ConnectionError.dns_core.data
122
+ r1_data = r1.json()
123
+ self.__dns_gn__ipv4 = r1_data['ip'] + ':' + str(r1_data['port'])
124
+
125
+
126
+ r2 = await httpxAsyncClient.request('GET', f'https://{self.__dns_gn__ipv4}/gn/getIp?d={domain}')
127
+ except httpx.TimeoutException:
128
+ raise GNExceptions.ConnectionError.dns_core.timeout
129
+ except:
130
+ raise GNExceptions.ConnectionError.dns_core.connection
131
+
132
+ if r2.status_code != 200:
133
+ raise GNExceptions.ConnectionError.dns_core.data
134
+
135
+ r2_data = r2.json()
136
+
137
+ return r2_data
138
+
139
+ def addRequestCallback(self, callback: Callable, name: str):
140
+ self.__request_callbacks[name] = callback
141
+
142
+ def addResponseCallback(self, callback: Callable, name: str):
143
+ self.__response_callbacks[name] = callback
144
+
145
+
146
+ async def connect(self, domain: str):
147
+ if domain in self._active_connections:
148
+ return
149
+
150
+ data = await self._getCoreDNS(domain)
151
+ # подключаемся к серверу gn-proxy
152
+ await self._client.connect(data['ip'], data['port'])
153
+ self._active_connections[domain] = 'active'
154
+
155
+ async def disconnect(self):
156
+ await self._client.disconnect()
157
+
158
+
159
+ def _return_token(self, bigToken: str, s: bool = True) -> str:
160
+ return bigToken[:128] if s else bigToken[128:]
161
+
162
+
163
+ async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> GNResponse:
164
+ """
165
+ Build and send a async request.
166
+
167
+ ```python
168
+ gnAsyncClient = KeyisBClient.AsyncClient()
169
+ async def func():
170
+ response = await gnAsyncClient.request(GNRequest('GET', Url('gn://example.com/example')))
171
+ command = response.command()
172
+ data = response.payload()
173
+ ```
174
+ """
175
+
176
+
177
+
178
+
179
+
180
+
181
+ if isinstance(request, GNRequest):
182
+
183
+
184
+ if request.url.hostname not in self._active_connections:
185
+ await self.connect(request.url.hostname)
186
+
187
+
188
+
189
+ for f in self.__request_callbacks.values():
190
+ asyncio.create_task(f(request))
191
+
192
+ r = await self._client.asyncRequest(request)
193
+
194
+ for f in self.__response_callbacks.values():
195
+ asyncio.create_task(f(r))
196
+
197
+ return r
198
+
199
+ # else:
200
+ # async def wrapped(request) -> AsyncGenerator[GNRequest, None]:
201
+ # async for req in request:
202
+ # if req.gn_protocol is None:
203
+ # req.setGNProtocol(self.__current_session['protocols'][0])
204
+ # req._stream = True
205
+
206
+ # for f in self.__request_callbacks.values():
207
+ # asyncio.create_task(f(req))
208
+
209
+ # yield req
210
+ # r = await self.client.asyncRequest(wrapped(request))
211
+
212
+ # for f in self.__response_callbacks.values():
213
+ # asyncio.create_task(f(r))
214
+
215
+ # return r
216
+
217
+ async def requestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> AsyncGenerator[GNResponse, None]:
218
+ """
219
+ Build and send a async request.
220
+ """
221
+ if isinstance(request, GNRequest):
222
+ if request.gn_protocol is None:
223
+ request.setGNProtocol(self.__current_session['protocols'][0])
224
+
225
+ for f in self.__request_callbacks.values():
226
+ asyncio.create_task(f(request))
227
+
228
+ async for response in self.client.asyncRequestStream(request):
229
+
230
+ for f in self.__response_callbacks.values():
231
+ asyncio.create_task(f(response))
232
+
233
+ yield response
234
+ else:
235
+ async def wrapped(request) -> AsyncGenerator[GNRequest, None]:
236
+ async for req in request:
237
+ if req.gn_protocol is None:
238
+ req.setGNProtocol(self.__current_session['protocols'][0])
239
+
240
+ for f in self.__request_callbacks.values():
241
+ asyncio.create_task(f(req))
242
+
243
+ req._stream = True
244
+ yield req
245
+ async for response in self.client.asyncRequestStream(wrapped(request)):
246
+
247
+ for f in self.__response_callbacks.values():
248
+ asyncio.create_task(f(response))
249
+
250
+ yield response
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+ # gn:quik
261
+
262
+ from aioquic.asyncio.protocol import QuicConnectionProtocol
263
+ from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
264
+ from aioquic.quic.connection import END_STATES
265
+ import asyncio
266
+ from collections import deque
267
+ from typing import Dict, Deque, Tuple, Optional, List
268
+
269
+ # ---------------------------------------------------------------------------
270
+ # Raw QUIC client with a dedicated SYS‑stream that consumes ~90 % CWND
271
+ # ---------------------------------------------------------------------------
272
+ # Основная идея:
273
+ # • Один постоянный bidirectional stream (sys_stream_id) используется для
274
+ # служебных сообщений.
275
+ # • Остальные запросы открываются в обычных потоках (user streams).
276
+ # • Отправка данных идёт через собственный scheduler: берём 9 «квантов» из
277
+ # SYS‑очереди и 1 квант из USER‑очереди, пока есть SYS‑данные.
278
+ # • Таким образом SYS‑канал получает ~90 % пропускной способности.
279
+ # ---------------------------------------------------------------------------
280
+
281
+ import asyncio
282
+ import time
283
+ from collections import deque
284
+ from dataclasses import dataclass
285
+ from itertools import count
286
+ from typing import Deque, Dict, Optional, Tuple, Union
287
+
288
+ from aioquic.quic.configuration import QuicConfiguration
289
+ from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
290
+
291
+
292
+ class RawQuicClient(QuicConnectionProtocol):
293
+ """Чистый‑QUIC клиент с приоритизированным SYS‑каналом + стриминг."""
294
+
295
+ SYS_RATIO_NUM = 9 # SYS 9/10
296
+ SYS_RATIO_DEN = 10
297
+ KEEPALIVE_INTERVAL = 10 # сек
298
+ KEEPALIVE_IDLE_TRIGGER = 30 # сек
299
+
300
+ # ────────────────────────────────────────────────────────────────── init ─┐
301
+ def __init__(self, *args, **kwargs):
302
+ super().__init__(*args, **kwargs)
303
+
304
+ self._sys_stream_id: Optional[int] = None
305
+ self._queue_sys: Deque[Tuple[int, bytes, bool]] = deque()
306
+ self._queue_user: Deque[Tuple[int, bytes, bool]] = deque()
307
+
308
+ # <‑‑ Future | Queue[bytes | None]
309
+ self._inflight: Dict[int, Union[asyncio.Future, asyncio.Queue[Optional[GNResponse]]]] = {}
310
+ self._inflight_streams: Dict[int, bytearray] = {}
311
+ self._sys_inflight: Dict[int, asyncio.Future] = {}
312
+ self._buffer: Dict[Union[int, str], bytearray] = {}
313
+
314
+ self._sys_budget = self.SYS_RATIO_NUM
315
+ self._sys_id_gen = count(1) # int64 message‑id generator
316
+
317
+ self._last_activity = time.time()
318
+ self._running = True
319
+ self._ping_id_gen = count(1) # int64 ping‑id generator
320
+ asyncio.create_task(self._keepalive_loop())
321
+
322
+ # ───────────────────────────────────────── private helpers ─┤
323
+ def _activity(self):
324
+ self._last_activity = time.time()
325
+
326
+ async def _keepalive_loop(self):
327
+ while self._running:
328
+ await asyncio.sleep(self.KEEPALIVE_INTERVAL)
329
+ idle_time = time.time() - self._last_activity
330
+ if idle_time > self.KEEPALIVE_IDLE_TRIGGER:
331
+ self._quic.send_ping(next(self._ping_id_gen))
332
+ self.transmit()
333
+ self._last_activity = time.time()
334
+
335
+ def stop(self):
336
+ self._running = False
337
+
338
+ # ───────────────────────────────────────────── events ─┤
339
+ def quic_event_received(self, event: QuicEvent) -> None: # noqa: C901
340
+ # ─── DATA ───────────────────────────────────────────
341
+ if isinstance(event, StreamDataReceived):
342
+ #print(event)
343
+ # SYS поток
344
+ if event.stream_id == self._sys_stream_id:
345
+ buf = self._buffer.setdefault("sys", bytearray())
346
+ buf.extend(event.data)
347
+ while True:
348
+ if len(buf) < 12:
349
+ break
350
+ msg_id = int.from_bytes(buf[:8], "little")
351
+ size = int.from_bytes(buf[8:12], "little")
352
+ if len(buf) < 12 + size:
353
+ break
354
+ payload = bytes(buf[12 : 12 + size])
355
+ del buf[: 12 + size]
356
+ fut = self._sys_inflight.pop(msg_id, None) if msg_id else None
357
+ if fut and not fut.done():
358
+ fut.set_result(payload)
359
+ # USER поток
360
+ else:
361
+ handler = self._inflight.get(event.stream_id)
362
+ if handler is None:
363
+ return
364
+
365
+ # Чтение в зависимости от режима
366
+ if isinstance(handler, asyncio.Queue): # стрим от сервера
367
+ # получаем байты
368
+
369
+ buf = self._buffer.setdefault(event.stream_id, bytearray())
370
+ buf.extend(event.data)
371
+
372
+ if len(buf) < 8: # не дошел даже frame пакета
373
+ return
374
+
375
+ # получаем длинну пакета
376
+ mode, stream, lenght = GNResponse.type(buf)
377
+
378
+ if mode != 4: # не наш пакет
379
+ self._buffer.pop(event.stream_id)
380
+ return
381
+
382
+ if not stream: # клиент просил стрим, а сервер прислал один пакет
383
+ self._buffer.pop(event.stream_id)
384
+ return
385
+
386
+ # читаем пакет
387
+ if len(buf) < lenght: # если пакет не весь пришел, пропускаем
388
+ return
389
+
390
+ # пакет пришел весь
391
+
392
+ # берем пакет
393
+ data = buf[:lenght]
394
+
395
+ # удаляем его из буфера
396
+ del buf[:lenght]
397
+
398
+
399
+ r = GNResponse.deserialize(data, 2)
400
+ handler.put_nowait(r)
401
+ if event.end_stream:
402
+ handler.put_nowait(None)
403
+ self._buffer.pop(event.stream_id)
404
+ self._inflight.pop(event.stream_id, None)
405
+
406
+
407
+
408
+ else: # Future
409
+ buf = self._buffer.setdefault(event.stream_id, bytearray())
410
+ buf.extend(event.data)
411
+ if event.end_stream:
412
+ self._inflight.pop(event.stream_id, None)
413
+ data = bytes(self._buffer.pop(event.stream_id, b""))
414
+ if not handler.done():
415
+ handler.set_result(data)
416
+
417
+ # ─── RESET ──────────────────────────────────────────
418
+ elif isinstance(event, StreamReset):
419
+ handler = self._inflight.pop(event.stream_id, None) or self._sys_inflight.pop(
420
+ event.stream_id, None
421
+ )
422
+ if handler is None:
423
+ return
424
+ if isinstance(handler, asyncio.Queue):
425
+ handler.put_nowait(None)
426
+ else:
427
+ if not handler.done():
428
+ handler.set_exception(RuntimeError("stream reset"))
429
+
430
+ # ─────────────────────────────────────────── scheduler ─┤
431
+ def _enqueue(self, sid: int, blob: bytes, end_stream: bool, is_sys: bool):
432
+ (self._queue_sys if is_sys else self._queue_user).append((sid, blob, end_stream))
433
+
434
+ def _schedule_flush(self):
435
+ while (self._queue_sys or self._queue_user) and self._quic._close_event is None:
436
+ q = None
437
+ if self._queue_sys and (self._sys_budget > 0 or not self._queue_user):
438
+ q = self._queue_sys
439
+ self._sys_budget -= 1
440
+ elif self._queue_user:
441
+ q = self._queue_user
442
+ self._sys_budget = self.SYS_RATIO_NUM
443
+ if q is None:
444
+ break
445
+ sid, blob, end_stream = q.popleft()
446
+ self._quic.send_stream_data(sid, blob, end_stream=end_stream)
447
+ self.transmit()
448
+ self._activity()
449
+
450
+ # ─────────────────────────────────────────── public API ─┤
451
+ async def ensure_sys_stream(self):
452
+ if self._sys_stream_id is None:
453
+ self._sys_stream_id = self._quic.get_next_available_stream_id()
454
+ self._enqueue(self._sys_stream_id, b"", False, True) # dummy
455
+ self._schedule_flush()
456
+
457
+ async def send_sys(self, request: GNRequest, response: bool = False) -> Optional[bytes]:
458
+ await self.ensure_sys_stream()
459
+ if response:
460
+ msg_id = next(self._sys_id_gen)
461
+ blob = request.serialize(2)
462
+ payload = (
463
+ msg_id.to_bytes(8, "little") + len(blob).to_bytes(4, "little") + blob
464
+ )
465
+ fut = asyncio.get_running_loop().create_future()
466
+ self._sys_inflight[msg_id] = fut
467
+ self._enqueue(self._sys_stream_id, payload, False, True)
468
+ self._schedule_flush()
469
+ return await fut
470
+ payload = (0).to_bytes(8, "little") + request.serialize(2)
471
+ self._enqueue(self._sys_stream_id, payload, False, True)
472
+ self._schedule_flush()
473
+ return None
474
+
475
+ async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]):
476
+ if isinstance(request, GNRequest):
477
+ blob = request.serialize(2)
478
+ sid = self._quic.get_next_available_stream_id()
479
+ self._enqueue(sid, blob, True, False)
480
+ self._schedule_flush()
481
+
482
+
483
+ fut = asyncio.get_running_loop().create_future()
484
+ self._inflight[sid] = fut
485
+ return await fut
486
+
487
+ else:
488
+ sid = self._quic.get_next_available_stream_id()
489
+ #if sid in self._quic._streams and not self._quic._streams[sid].is_finished:
490
+
491
+ async def _stream_sender(sid, request: AsyncGenerator[GNRequest, Any]):
492
+ _last = None
493
+ async for req in request:
494
+ _last = req
495
+ blob = req.serialize(2)
496
+ self._enqueue(sid, blob, False, False)
497
+
498
+
499
+ self._schedule_flush()
500
+
501
+ print(f'Отправлен stream запрос {req}')
502
+
503
+
504
+ _last.setPayload(None)
505
+ _last.setMethod('gn:end-stream')
506
+ blob = _last.serialize(2)
507
+ self._enqueue(sid, blob, True, False)
508
+ self._schedule_flush()
509
+
510
+ asyncio.create_task(_stream_sender(sid, request))
511
+
512
+
513
+ fut = asyncio.get_running_loop().create_future()
514
+ self._inflight[sid] = fut
515
+ return await fut
516
+
517
+ async def requestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> asyncio.Queue[GNResponse]:
518
+ if isinstance(request, GNRequest):
519
+ blob = request.serialize(2)
520
+ sid = self._quic.get_next_available_stream_id()
521
+ self._enqueue(sid, blob, False, False)
522
+ self._schedule_flush()
523
+
524
+
525
+ q = asyncio.Queue()
526
+ self._inflight[sid] = q
527
+ return q
528
+
529
+ else:
530
+ sid = self._quic.get_next_available_stream_id()
531
+
532
+ async def _stream_sender(sid, request: AsyncGenerator[GNRequest, Any]):
533
+ _last = None
534
+ async for req in request:
535
+ _last = req
536
+ blob = req.serialize(2)
537
+ self._enqueue(sid, blob, False, False)
538
+
539
+
540
+ self._schedule_flush()
541
+
542
+ print(f'Отправлен stream запрос {req}')
543
+
544
+
545
+ _last.setPayload(None)
546
+ _last.setMethod('gn:end-stream')
547
+ blob = _last.serialize(2)
548
+ self._enqueue(sid, blob, True, False)
549
+ self._schedule_flush()
550
+
551
+ asyncio.create_task(_stream_sender(sid, request))
552
+
553
+
554
+ q = asyncio.Queue()
555
+ self._inflight[sid] = q
556
+ return q
557
+
558
+
559
+
560
+ class QuicClient:
561
+ """Обёртка‑фасад над RawQuicClient."""
562
+
563
+ def __init__(self):
564
+ self._quik_core: Optional[RawQuicClient] = None
565
+ self._client_cm = None
566
+
567
+ async def connect(self, ip: str, port: int):
568
+ cfg = QuicConfiguration(is_client=True, alpn_protocols=["gn:backend"])
569
+ cfg.load_verify_locations(KeyisBClient.ssl_gw_crt_path)
570
+
571
+ self._client_cm = connect(
572
+ ip,
573
+ port,
574
+ configuration=cfg,
575
+ create_protocol=RawQuicClient,
576
+ wait_connected=True,
577
+ )
578
+ self._quik_core = await self._client_cm.__aenter__()
579
+
580
+ async def disconnect(self):
581
+ self._quik_core.close()
582
+ await self._quik_core.wait_closed()
583
+ self._quik_core = None
584
+
585
+ def syncRequest(self, request: GNRequest):
586
+ return asyncio.get_event_loop().run_until_complete(self.asyncRequest(request))
587
+
588
+ async def asyncRequest(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> GNResponse:
589
+ if self._quik_core is None:
590
+ raise RuntimeError("Not connected")
591
+
592
+ resp = await self._quik_core.request(request)
593
+ return GNResponse.deserialize(resp, 2)
594
+
595
+ async def asyncRequestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> AsyncGenerator[GNResponse, None]:
596
+
597
+ if self._quik_core is None:
598
+ raise RuntimeError("Not connected")
599
+
600
+ queue = await self._quik_core.requestStream(request)
601
+
602
+ while True:
603
+ chunk = await queue.get()
604
+ if chunk is None or chunk.command == 'gn:end-stream':
605
+ break
606
+ yield chunk
607
+
608
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.13
3
+ Version: 0.0.0.0.15
4
4
  Summary: GNServer
5
5
  Home-page: https://github.com/KeyisB/libs/tree/main/GNServer
6
6
  Author: KeyisB
@@ -5,6 +5,7 @@ GNServer/LICENSE
5
5
  GNServer/mmbConfig.json
6
6
  GNServer/GNServer/__init__.py
7
7
  GNServer/GNServer/_app.py
8
+ GNServer/GNServer/_client.py
8
9
  GNServer/GNServer.egg-info/PKG-INFO
9
10
  GNServer/GNServer.egg-info/SOURCES.txt
10
11
  GNServer/GNServer.egg-info/dependency_links.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.13
3
+ Version: 0.0.0.0.15
4
4
  Summary: GNServer
5
5
  Home-page: https://github.com/KeyisB/libs/tree/main/GNServer
6
6
  Author: KeyisB
@@ -5,7 +5,7 @@ filesName = 'GNServer'
5
5
 
6
6
  setup(
7
7
  name=name,
8
- version='0.0.0.0.13',
8
+ version='0.0.0.0.15',
9
9
  author="KeyisB",
10
10
  author_email="keyisb.pip@gmail.com",
11
11
  description=name,
File without changes
File without changes
File without changes