GNServer 0.0.0.0.17__py3-none-any.whl → 0.0.0.0.19__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.
- GNServer/___client.py +628 -0
- GNServer/_app.py +38 -10
- GNServer/_client.py +127 -62
- {gnserver-0.0.0.0.17.dist-info → gnserver-0.0.0.0.19.dist-info}/METADATA +1 -1
- gnserver-0.0.0.0.19.dist-info/RECORD +9 -0
- gnserver-0.0.0.0.17.dist-info/RECORD +0 -8
- {gnserver-0.0.0.0.17.dist-info → gnserver-0.0.0.0.19.dist-info}/WHEEL +0 -0
- {gnserver-0.0.0.0.17.dist-info → gnserver-0.0.0.0.19.dist-info}/licenses/LICENSE +0 -0
- {gnserver-0.0.0.0.17.dist-info → gnserver-0.0.0.0.19.dist-info}/top_level.txt +0 -0
GNServer/___client.py
ADDED
@@ -0,0 +1,628 @@
|
|
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, Literal
|
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._active_connections: Dict[str, QuicClient] = {}
|
105
|
+
|
106
|
+
async def _getCoreDNS(self, domain: str):
|
107
|
+
|
108
|
+
if domain.split('.')[-1].split(':')[0].isdigit() and domain.split(':')[-1].isdigit():
|
109
|
+
r2_data = {
|
110
|
+
"ip": domain.split(':')[0],
|
111
|
+
"port": int(domain.split(':')[-1])
|
112
|
+
}
|
113
|
+
return r2_data
|
114
|
+
|
115
|
+
try:
|
116
|
+
if self.__dns_gn__ipv4 is None:
|
117
|
+
r1 = await httpxAsyncClient.request('GET', f'https://{self.__dns_core__ipv4}/gn/getIp?d=dns.gn')
|
118
|
+
if r1.status_code != 200:
|
119
|
+
raise GNExceptions.ConnectionError.dns_core.data
|
120
|
+
r1_data = r1.json()
|
121
|
+
self.__dns_gn__ipv4 = r1_data['ip'] + ':' + str(r1_data['port'])
|
122
|
+
|
123
|
+
|
124
|
+
r2 = await httpxAsyncClient.request('GET', f'https://{self.__dns_gn__ipv4}/gn/getIp?d={domain}')
|
125
|
+
except httpx.TimeoutException:
|
126
|
+
raise GNExceptions.ConnectionError.dns_core.timeout
|
127
|
+
except:
|
128
|
+
raise GNExceptions.ConnectionError.dns_core.connection
|
129
|
+
|
130
|
+
if r2.status_code != 200:
|
131
|
+
raise GNExceptions.ConnectionError.dns_core.data
|
132
|
+
|
133
|
+
r2_data = r2.json()
|
134
|
+
|
135
|
+
return r2_data
|
136
|
+
|
137
|
+
def addRequestCallback(self, callback: Callable, name: str):
|
138
|
+
self.__request_callbacks[name] = callback
|
139
|
+
|
140
|
+
def addResponseCallback(self, callback: Callable, name: str):
|
141
|
+
self.__response_callbacks[name] = callback
|
142
|
+
|
143
|
+
|
144
|
+
async def connect(self, domain: str, restart_connection: bool = False, reconnect_wait: float = 10) -> 'QuicClient':
|
145
|
+
print('Запрос подключения')
|
146
|
+
if not restart_connection and domain in self._active_connections:
|
147
|
+
print('Подключение уже было')
|
148
|
+
c = self._active_connections[domain]
|
149
|
+
if c.status == 'connecting':
|
150
|
+
if (c.connection_time + datetime.timedelta(seconds=11)) < datetime.datetime.now():
|
151
|
+
print('ждем поделючения')
|
152
|
+
try:
|
153
|
+
await asyncio.wait_for(c.connect_future, reconnect_wait)
|
154
|
+
print('дождались')
|
155
|
+
return c
|
156
|
+
except:
|
157
|
+
print('Заново соеденяемся...')
|
158
|
+
|
159
|
+
else:
|
160
|
+
return c
|
161
|
+
|
162
|
+
c = QuicClient()
|
163
|
+
c.status = 'connecting'
|
164
|
+
self._active_connections[domain] = c
|
165
|
+
|
166
|
+
data = await self._getCoreDNS(domain)
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
def f(domain):
|
171
|
+
self._active_connections.pop(domain)
|
172
|
+
|
173
|
+
c._disconnect_signal = f
|
174
|
+
c._domain = domain
|
175
|
+
|
176
|
+
await c.connect(data['ip'], data['port'])
|
177
|
+
await c.connect_future
|
178
|
+
|
179
|
+
return c
|
180
|
+
|
181
|
+
async def disconnect(self, domain):
|
182
|
+
if domain not in self._active_connections:
|
183
|
+
return
|
184
|
+
|
185
|
+
await self._active_connections[domain].disconnect()
|
186
|
+
|
187
|
+
|
188
|
+
def _return_token(self, bigToken: str, s: bool = True) -> str:
|
189
|
+
return bigToken[:128] if s else bigToken[128:]
|
190
|
+
|
191
|
+
|
192
|
+
async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]], restart_connection: bool = False, reconnect_wait: float = 10) -> GNResponse:
|
193
|
+
if isinstance(request, GNRequest):
|
194
|
+
|
195
|
+
|
196
|
+
c = await self.connect(request.url.hostname, restart_connection, reconnect_wait)
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
for f in self.__request_callbacks.values():
|
201
|
+
asyncio.create_task(f(request))
|
202
|
+
|
203
|
+
r = await c.asyncRequest(request)
|
204
|
+
|
205
|
+
for f in self.__response_callbacks.values():
|
206
|
+
asyncio.create_task(f(r))
|
207
|
+
|
208
|
+
return r
|
209
|
+
|
210
|
+
# else:
|
211
|
+
# async def wrapped(request) -> AsyncGenerator[GNRequest, None]:
|
212
|
+
# async for req in request:
|
213
|
+
# if req.gn_protocol is None:
|
214
|
+
# req.setGNProtocol(self.__current_session['protocols'][0])
|
215
|
+
# req._stream = True
|
216
|
+
|
217
|
+
# for f in self.__request_callbacks.values():
|
218
|
+
# asyncio.create_task(f(req))
|
219
|
+
|
220
|
+
# yield req
|
221
|
+
# r = await self.client.asyncRequest(wrapped(request))
|
222
|
+
|
223
|
+
# for f in self.__response_callbacks.values():
|
224
|
+
# asyncio.create_task(f(r))
|
225
|
+
|
226
|
+
# return r
|
227
|
+
|
228
|
+
async def requestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> AsyncGenerator[GNResponse, None]:
|
229
|
+
"""
|
230
|
+
Build and send a async request.
|
231
|
+
"""
|
232
|
+
if isinstance(request, GNRequest):
|
233
|
+
if request.gn_protocol is None:
|
234
|
+
request.setGNProtocol(self.__current_session['protocols'][0])
|
235
|
+
|
236
|
+
for f in self.__request_callbacks.values():
|
237
|
+
asyncio.create_task(f(request))
|
238
|
+
|
239
|
+
async for response in self.client.asyncRequestStream(request):
|
240
|
+
|
241
|
+
for f in self.__response_callbacks.values():
|
242
|
+
asyncio.create_task(f(response))
|
243
|
+
|
244
|
+
yield response
|
245
|
+
else:
|
246
|
+
async def wrapped(request) -> AsyncGenerator[GNRequest, None]:
|
247
|
+
async for req in request:
|
248
|
+
if req.gn_protocol is None:
|
249
|
+
req.setGNProtocol(self.__current_session['protocols'][0])
|
250
|
+
|
251
|
+
for f in self.__request_callbacks.values():
|
252
|
+
asyncio.create_task(f(req))
|
253
|
+
|
254
|
+
req._stream = True
|
255
|
+
yield req
|
256
|
+
async for response in self.client.asyncRequestStream(wrapped(request)):
|
257
|
+
|
258
|
+
for f in self.__response_callbacks.values():
|
259
|
+
asyncio.create_task(f(response))
|
260
|
+
|
261
|
+
yield response
|
262
|
+
|
263
|
+
from aioquic.asyncio.protocol import QuicConnectionProtocol
|
264
|
+
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
|
265
|
+
from aioquic.quic.connection import END_STATES
|
266
|
+
import asyncio
|
267
|
+
from collections import deque
|
268
|
+
from typing import Dict, Deque, Tuple, Optional, List
|
269
|
+
|
270
|
+
import asyncio
|
271
|
+
import time
|
272
|
+
from collections import deque
|
273
|
+
from dataclasses import dataclass
|
274
|
+
from itertools import count
|
275
|
+
from typing import Deque, Dict, Optional, Tuple, Union
|
276
|
+
|
277
|
+
from aioquic.quic.configuration import QuicConfiguration
|
278
|
+
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset, ConnectionTerminated
|
279
|
+
|
280
|
+
|
281
|
+
class RawQuicClient(QuicConnectionProtocol):
|
282
|
+
|
283
|
+
SYS_RATIO_NUM = 9 # SYS 9/10
|
284
|
+
SYS_RATIO_DEN = 10
|
285
|
+
KEEPALIVE_INTERVAL = 10 # сек
|
286
|
+
KEEPALIVE_IDLE_TRIGGER = 30 # сек
|
287
|
+
|
288
|
+
# ────────────────────────────────────────────────────────────────── init ─┐
|
289
|
+
def __init__(self, *args, **kwargs):
|
290
|
+
super().__init__(*args, **kwargs)
|
291
|
+
|
292
|
+
self.quicClient: QuicClient = None
|
293
|
+
|
294
|
+
self._sys_stream_id: Optional[int] = None
|
295
|
+
self._queue_sys: Deque[Tuple[int, bytes, bool]] = deque()
|
296
|
+
self._queue_user: Deque[Tuple[int, bytes, bool]] = deque()
|
297
|
+
|
298
|
+
# <‑‑ Future | Queue[bytes | None]
|
299
|
+
self._inflight: Dict[int, Union[asyncio.Future, asyncio.Queue[Optional[GNResponse]]]] = {}
|
300
|
+
self._inflight_streams: Dict[int, bytearray] = {}
|
301
|
+
self._sys_inflight: Dict[int, asyncio.Future] = {}
|
302
|
+
self._buffer: Dict[Union[int, str], bytearray] = {}
|
303
|
+
|
304
|
+
self._sys_budget = self.SYS_RATIO_NUM
|
305
|
+
self._sys_id_gen = count(1) # int64 message‑id generator
|
306
|
+
|
307
|
+
self._last_activity = time.time()
|
308
|
+
self._running = True
|
309
|
+
self._ping_id_gen = count(1) # int64 ping‑id generator
|
310
|
+
asyncio.create_task(self._keepalive_loop())
|
311
|
+
|
312
|
+
# ───────────────────────────────────────── private helpers ─┤
|
313
|
+
def _activity(self):
|
314
|
+
self._last_activity = time.time()
|
315
|
+
|
316
|
+
async def _keepalive_loop(self):
|
317
|
+
while self._running:
|
318
|
+
await asyncio.sleep(self.KEEPALIVE_INTERVAL)
|
319
|
+
idle_time = time.time() - self._last_activity
|
320
|
+
if idle_time > self.KEEPALIVE_IDLE_TRIGGER:
|
321
|
+
self._quic.send_ping(next(self._ping_id_gen))
|
322
|
+
self.transmit()
|
323
|
+
self._last_activity = time.time()
|
324
|
+
|
325
|
+
def stop(self):
|
326
|
+
self._running = False
|
327
|
+
|
328
|
+
# ───────────────────────────────────────────── events ─┤
|
329
|
+
def quic_event_received(self, event: QuicEvent) -> None: # noqa: C901
|
330
|
+
# ─── DATA ───────────────────────────────────────────
|
331
|
+
if isinstance(event, StreamDataReceived):
|
332
|
+
#print(event)
|
333
|
+
# SYS поток
|
334
|
+
if event.stream_id == self._sys_stream_id:
|
335
|
+
buf = self._buffer.setdefault("sys", bytearray())
|
336
|
+
buf.extend(event.data)
|
337
|
+
while True:
|
338
|
+
if len(buf) < 12:
|
339
|
+
break
|
340
|
+
msg_id = int.from_bytes(buf[:8], "little")
|
341
|
+
size = int.from_bytes(buf[8:12], "little")
|
342
|
+
if len(buf) < 12 + size:
|
343
|
+
break
|
344
|
+
payload = bytes(buf[12 : 12 + size])
|
345
|
+
del buf[: 12 + size]
|
346
|
+
fut = self._sys_inflight.pop(msg_id, None) if msg_id else None
|
347
|
+
if fut and not fut.done():
|
348
|
+
fut.set_result(payload)
|
349
|
+
# USER поток
|
350
|
+
else:
|
351
|
+
handler = self._inflight.get(event.stream_id)
|
352
|
+
if handler is None:
|
353
|
+
return
|
354
|
+
|
355
|
+
# Чтение в зависимости от режима
|
356
|
+
if isinstance(handler, asyncio.Queue): # стрим от сервера
|
357
|
+
# получаем байты
|
358
|
+
|
359
|
+
buf = self._buffer.setdefault(event.stream_id, bytearray())
|
360
|
+
buf.extend(event.data)
|
361
|
+
|
362
|
+
if len(buf) < 8: # не дошел даже frame пакета
|
363
|
+
return
|
364
|
+
|
365
|
+
# получаем длинну пакета
|
366
|
+
mode, stream, lenght = GNResponse.type(buf)
|
367
|
+
|
368
|
+
if mode != 4: # не наш пакет
|
369
|
+
self._buffer.pop(event.stream_id)
|
370
|
+
return
|
371
|
+
|
372
|
+
if not stream: # клиент просил стрим, а сервер прислал один пакет
|
373
|
+
self._buffer.pop(event.stream_id)
|
374
|
+
return
|
375
|
+
|
376
|
+
# читаем пакет
|
377
|
+
if len(buf) < lenght: # если пакет не весь пришел, пропускаем
|
378
|
+
return
|
379
|
+
|
380
|
+
# пакет пришел весь
|
381
|
+
|
382
|
+
# берем пакет
|
383
|
+
data = buf[:lenght]
|
384
|
+
|
385
|
+
# удаляем его из буфера
|
386
|
+
del buf[:lenght]
|
387
|
+
|
388
|
+
|
389
|
+
r = GNResponse.deserialize(data, 2)
|
390
|
+
handler.put_nowait(r)
|
391
|
+
if event.end_stream:
|
392
|
+
handler.put_nowait(None)
|
393
|
+
self._buffer.pop(event.stream_id)
|
394
|
+
self._inflight.pop(event.stream_id, None)
|
395
|
+
|
396
|
+
|
397
|
+
|
398
|
+
else: # Future
|
399
|
+
buf = self._buffer.setdefault(event.stream_id, bytearray())
|
400
|
+
buf.extend(event.data)
|
401
|
+
if event.end_stream:
|
402
|
+
self._inflight.pop(event.stream_id, None)
|
403
|
+
data = bytes(self._buffer.pop(event.stream_id, b""))
|
404
|
+
if not handler.done():
|
405
|
+
handler.set_result(data)
|
406
|
+
|
407
|
+
# ─── RESET ──────────────────────────────────────────
|
408
|
+
elif isinstance(event, StreamReset):
|
409
|
+
handler = self._inflight.pop(event.stream_id, None) or self._sys_inflight.pop(
|
410
|
+
event.stream_id, None
|
411
|
+
)
|
412
|
+
if handler is None:
|
413
|
+
return
|
414
|
+
if isinstance(handler, asyncio.Queue):
|
415
|
+
handler.put_nowait(None)
|
416
|
+
else:
|
417
|
+
if not handler.done():
|
418
|
+
handler.set_exception(RuntimeError("stream reset"))
|
419
|
+
|
420
|
+
|
421
|
+
elif isinstance(event, ConnectionTerminated):
|
422
|
+
print("QUIC connection closed")
|
423
|
+
print("Error code:", event.error_code)
|
424
|
+
print("Reason:", event.reason_phrase)
|
425
|
+
if self.quicClient is None:
|
426
|
+
return
|
427
|
+
|
428
|
+
asyncio.create_task(self.quicClient.disconnect())
|
429
|
+
|
430
|
+
|
431
|
+
# ─────────────────────────────────────────── scheduler ─┤
|
432
|
+
def _enqueue(self, sid: int, blob: bytes, end_stream: bool, is_sys: bool):
|
433
|
+
(self._queue_sys if is_sys else self._queue_user).append((sid, blob, end_stream))
|
434
|
+
|
435
|
+
def _schedule_flush(self):
|
436
|
+
while (self._queue_sys or self._queue_user) and self._quic._close_event is None:
|
437
|
+
q = None
|
438
|
+
if self._queue_sys and (self._sys_budget > 0 or not self._queue_user):
|
439
|
+
q = self._queue_sys
|
440
|
+
self._sys_budget -= 1
|
441
|
+
elif self._queue_user:
|
442
|
+
q = self._queue_user
|
443
|
+
self._sys_budget = self.SYS_RATIO_NUM
|
444
|
+
if q is None:
|
445
|
+
break
|
446
|
+
sid, blob, end_stream = q.popleft()
|
447
|
+
print(f'Отправка стрима {sid}')
|
448
|
+
self._quic.send_stream_data(sid, blob, end_stream=end_stream)
|
449
|
+
self.transmit()
|
450
|
+
self._activity()
|
451
|
+
|
452
|
+
# ─────────────────────────────────────────── public API ─┤
|
453
|
+
async def ensure_sys_stream(self):
|
454
|
+
if self._sys_stream_id is None:
|
455
|
+
self._sys_stream_id = self._quic.get_next_available_stream_id()
|
456
|
+
self._enqueue(self._sys_stream_id, b"", False, True) # dummy
|
457
|
+
self._schedule_flush()
|
458
|
+
|
459
|
+
async def send_sys(self, request: GNRequest, response: bool = False) -> Optional[bytes]:
|
460
|
+
await self.ensure_sys_stream()
|
461
|
+
if response:
|
462
|
+
msg_id = next(self._sys_id_gen)
|
463
|
+
blob = request.serialize(2)
|
464
|
+
payload = (
|
465
|
+
msg_id.to_bytes(8, "little") + len(blob).to_bytes(4, "little") + blob
|
466
|
+
)
|
467
|
+
fut = asyncio.get_running_loop().create_future()
|
468
|
+
self._sys_inflight[msg_id] = fut
|
469
|
+
self._enqueue(self._sys_stream_id, payload, False, True)
|
470
|
+
self._schedule_flush()
|
471
|
+
return await fut
|
472
|
+
payload = (0).to_bytes(8, "little") + request.serialize(2)
|
473
|
+
self._enqueue(self._sys_stream_id, payload, False, True)
|
474
|
+
self._schedule_flush()
|
475
|
+
return None
|
476
|
+
|
477
|
+
async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]):
|
478
|
+
if isinstance(request, GNRequest):
|
479
|
+
blob = request.serialize(2)
|
480
|
+
sid = self._quic.get_next_available_stream_id()
|
481
|
+
self._enqueue(sid, blob, True, False)
|
482
|
+
self._schedule_flush()
|
483
|
+
|
484
|
+
|
485
|
+
fut = asyncio.get_running_loop().create_future()
|
486
|
+
self._inflight[sid] = fut
|
487
|
+
return await fut
|
488
|
+
|
489
|
+
else:
|
490
|
+
sid = self._quic.get_next_available_stream_id()
|
491
|
+
#if sid in self._quic._streams and not self._quic._streams[sid].is_finished:
|
492
|
+
|
493
|
+
async def _stream_sender(sid, request: AsyncGenerator[GNRequest, Any]):
|
494
|
+
_last = None
|
495
|
+
async for req in request:
|
496
|
+
_last = req
|
497
|
+
blob = req.serialize(2)
|
498
|
+
self._enqueue(sid, blob, False, False)
|
499
|
+
|
500
|
+
|
501
|
+
self._schedule_flush()
|
502
|
+
|
503
|
+
print(f'Отправлен stream запрос {req}')
|
504
|
+
|
505
|
+
|
506
|
+
_last.setPayload(None)
|
507
|
+
_last.setMethod('gn:end-stream')
|
508
|
+
blob = _last.serialize(2)
|
509
|
+
self._enqueue(sid, blob, True, False)
|
510
|
+
self._schedule_flush()
|
511
|
+
|
512
|
+
asyncio.create_task(_stream_sender(sid, request))
|
513
|
+
|
514
|
+
|
515
|
+
fut = asyncio.get_running_loop().create_future()
|
516
|
+
self._inflight[sid] = fut
|
517
|
+
return await fut
|
518
|
+
|
519
|
+
async def requestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> asyncio.Queue[GNResponse]:
|
520
|
+
if isinstance(request, GNRequest):
|
521
|
+
blob = request.serialize(2)
|
522
|
+
sid = self._quic.get_next_available_stream_id()
|
523
|
+
self._enqueue(sid, blob, False, False)
|
524
|
+
self._schedule_flush()
|
525
|
+
|
526
|
+
|
527
|
+
q = asyncio.Queue()
|
528
|
+
self._inflight[sid] = q
|
529
|
+
return q
|
530
|
+
|
531
|
+
else:
|
532
|
+
sid = self._quic.get_next_available_stream_id()
|
533
|
+
|
534
|
+
async def _stream_sender(sid, request: AsyncGenerator[GNRequest, Any]):
|
535
|
+
_last = None
|
536
|
+
async for req in request:
|
537
|
+
_last = req
|
538
|
+
blob = req.serialize(2)
|
539
|
+
self._enqueue(sid, blob, False, False)
|
540
|
+
|
541
|
+
|
542
|
+
self._schedule_flush()
|
543
|
+
|
544
|
+
print(f'Отправлен stream запрос {req}')
|
545
|
+
|
546
|
+
|
547
|
+
_last.setPayload(None)
|
548
|
+
_last.setMethod('gn:end-stream')
|
549
|
+
blob = _last.serialize(2)
|
550
|
+
self._enqueue(sid, blob, True, False)
|
551
|
+
self._schedule_flush()
|
552
|
+
|
553
|
+
asyncio.create_task(_stream_sender(sid, request))
|
554
|
+
|
555
|
+
|
556
|
+
q = asyncio.Queue()
|
557
|
+
self._inflight[sid] = q
|
558
|
+
return q
|
559
|
+
|
560
|
+
|
561
|
+
|
562
|
+
class QuicClient:
|
563
|
+
"""Обёртка‑фасад над RawQuicClient."""
|
564
|
+
|
565
|
+
def __init__(self):
|
566
|
+
self._quik_core: Optional[RawQuicClient] = None
|
567
|
+
self._client_cm = None
|
568
|
+
self._disconnect_signal = None
|
569
|
+
self._domain = None
|
570
|
+
|
571
|
+
self.status: Literal['active', 'connecting', 'disconnect']
|
572
|
+
|
573
|
+
self.connect_future = asyncio.get_event_loop().create_future()
|
574
|
+
self.connection_time: datetime.datetime = None
|
575
|
+
|
576
|
+
async def connect(self, ip: str, port: int):
|
577
|
+
self.status = 'connecting'
|
578
|
+
self.connection_time = datetime.datetime.now()
|
579
|
+
cfg = QuicConfiguration(is_client=True, alpn_protocols=["gn:backend"])
|
580
|
+
cfg.load_verify_locations(KeyisBClient.ssl_gw_crt_path)
|
581
|
+
cfg.idle_timeout = 10
|
582
|
+
|
583
|
+
self._client_cm = connect(
|
584
|
+
ip,
|
585
|
+
port,
|
586
|
+
configuration=cfg,
|
587
|
+
create_protocol=RawQuicClient,
|
588
|
+
wait_connected=True,
|
589
|
+
)
|
590
|
+
self._quik_core = await self._client_cm.__aenter__()
|
591
|
+
self._quik_core.quicClient = self
|
592
|
+
|
593
|
+
self.status = 'active'
|
594
|
+
self.connect_future.set_result(True)
|
595
|
+
|
596
|
+
async def disconnect(self):
|
597
|
+
self.status = 'disconnect'
|
598
|
+
if self._disconnect_signal is not None:
|
599
|
+
self._disconnect_signal(self._domain)
|
600
|
+
|
601
|
+
self._quik_core.close()
|
602
|
+
await self._quik_core.wait_closed()
|
603
|
+
self._quik_core = None
|
604
|
+
|
605
|
+
def syncRequest(self, request: GNRequest):
|
606
|
+
return asyncio.get_event_loop().run_until_complete(self.asyncRequest(request))
|
607
|
+
|
608
|
+
async def asyncRequest(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> GNResponse:
|
609
|
+
if self._quik_core is None:
|
610
|
+
raise RuntimeError("Not connected")
|
611
|
+
|
612
|
+
resp = await self._quik_core.request(request)
|
613
|
+
return GNResponse.deserialize(resp, 2)
|
614
|
+
|
615
|
+
async def asyncRequestStream(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> AsyncGenerator[GNResponse, None]:
|
616
|
+
|
617
|
+
if self._quik_core is None:
|
618
|
+
raise RuntimeError("Not connected")
|
619
|
+
|
620
|
+
queue = await self._quik_core.requestStream(request)
|
621
|
+
|
622
|
+
while True:
|
623
|
+
chunk = await queue.get()
|
624
|
+
if chunk is None or chunk.command == 'gn:end-stream':
|
625
|
+
break
|
626
|
+
yield chunk
|
627
|
+
|
628
|
+
|
GNServer/_app.py
CHANGED
@@ -290,6 +290,7 @@ def _host_matches_pattern(host: str, pattern: str) -> bool:
|
|
290
290
|
|
291
291
|
|
292
292
|
|
293
|
+
|
293
294
|
@dataclass
|
294
295
|
class Route:
|
295
296
|
method: str
|
@@ -367,6 +368,15 @@ def _convert_value(raw: str | list[str], ann: Any, fallback: Callable[[str], Any
|
|
367
368
|
raw = [raw]
|
368
369
|
return [_convert_value(r, subtype, fallback) for r in raw]
|
369
370
|
|
371
|
+
# --- fix Union ---
|
372
|
+
if origin is Union:
|
373
|
+
for subtype in args:
|
374
|
+
try:
|
375
|
+
return _convert_value(raw, subtype, fallback)
|
376
|
+
except Exception:
|
377
|
+
continue
|
378
|
+
return raw # если ни один тип не подошёл
|
379
|
+
|
370
380
|
conv = _CONVERTER_FUNC.get(ann, ann) if ann is not inspect._empty else fallback
|
371
381
|
return conv(raw) if callable(conv) else raw
|
372
382
|
|
@@ -382,6 +392,8 @@ class App:
|
|
382
392
|
self._routes: List[Route] = []
|
383
393
|
self._cors: Optional[gn.CORSObject] = None
|
384
394
|
|
395
|
+
self.domain: str = None
|
396
|
+
|
385
397
|
def route(self, method: str, path: str, cors: Optional[gn.CORSObject] = None):
|
386
398
|
if path == '/':
|
387
399
|
path = ''
|
@@ -436,7 +448,7 @@ class App:
|
|
436
448
|
if r.method != method:
|
437
449
|
continue
|
438
450
|
|
439
|
-
if r.cors is not None:
|
451
|
+
if r.cors is not None and r.cors._allow_origins is not None:
|
440
452
|
if request._origin is None:
|
441
453
|
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url.'})
|
442
454
|
if not resolve_cors(request._origin, r.cors._allow_origins):
|
@@ -468,11 +480,16 @@ class App:
|
|
468
480
|
|
469
481
|
result = await r.handler(**kw)
|
470
482
|
if isinstance(result, gn.GNResponse):
|
471
|
-
if
|
472
|
-
result._cors
|
473
|
-
|
483
|
+
if r.cors is None:
|
484
|
+
if result._cors is None:
|
485
|
+
result._cors = self._cors
|
486
|
+
else:
|
487
|
+
result._cors = r.cors
|
488
|
+
|
489
|
+
if result._cors is not None and result._cors != r.cors and result._cors._allow_origins is not None:
|
474
490
|
if request._origin is None:
|
475
|
-
|
491
|
+
print(result._cors._allow_origins)
|
492
|
+
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url. [2]'})
|
476
493
|
if not resolve_cors(request._origin, result._cors._allow_origins):
|
477
494
|
return gn.GNResponse("gn:backend:802", {'error': 'Cors error: origin'})
|
478
495
|
if request.method not in result._cors._allow_methods and '*' not in result._cors._allow_methods:
|
@@ -520,9 +537,10 @@ class App:
|
|
520
537
|
|
521
538
|
|
522
539
|
def _init_sys_routes(self):
|
523
|
-
@self.
|
540
|
+
@self.post('/!gn-vm-host/ping', cors=gn.CORSObject(None))
|
524
541
|
async def r_ping(request: gn.GNRequest):
|
525
|
-
|
542
|
+
|
543
|
+
if request._client_ip != '127.0.0.1':
|
526
544
|
return gn.GNResponse('gn:backend:403', {'error': 'Forbidden'})
|
527
545
|
return gn.GNResponse('ok', {'time': datetime.datetime.now(datetime.UTC).isoformat()})
|
528
546
|
|
@@ -622,7 +640,7 @@ class App:
|
|
622
640
|
asyncio.create_task(self._handle_request(request, mode))
|
623
641
|
|
624
642
|
async def _handle_request(self, request: gn.GNRequest, mode: int):
|
625
|
-
|
643
|
+
|
626
644
|
request._client_ip = self._quic._network_paths[0].addr[0]
|
627
645
|
|
628
646
|
try:
|
@@ -667,15 +685,20 @@ class App:
|
|
667
685
|
|
668
686
|
def run(
|
669
687
|
self,
|
670
|
-
|
688
|
+
domain: str,
|
671
689
|
port: int,
|
672
690
|
cert_path: str,
|
673
691
|
key_path: str,
|
674
692
|
*,
|
693
|
+
host: Optional[str] = '0.0.0.0',
|
675
694
|
idle_timeout: float = 20.0,
|
676
|
-
wait: bool = True
|
695
|
+
wait: bool = True,
|
696
|
+
run: Optional[Callable] = None
|
677
697
|
):
|
678
698
|
|
699
|
+
self.domain = domain
|
700
|
+
|
701
|
+
|
679
702
|
self._init_sys_routes()
|
680
703
|
|
681
704
|
cfg = QuicConfiguration(
|
@@ -691,6 +714,11 @@ class App:
|
|
691
714
|
create_protocol=lambda *a, **kw: App._ServerProto(*a, api=self, **kw),
|
692
715
|
retry=False,
|
693
716
|
)
|
717
|
+
|
718
|
+
if run is not None:
|
719
|
+
await run()
|
720
|
+
|
721
|
+
|
694
722
|
if wait:
|
695
723
|
await asyncio.Event().wait()
|
696
724
|
|
GNServer/_client.py
CHANGED
@@ -9,7 +9,7 @@ import datetime
|
|
9
9
|
logging2.basicConfig(level=logging2.INFO)
|
10
10
|
|
11
11
|
from KeyisBLogging import logging
|
12
|
-
from typing import Dict, List, Tuple, Optional, cast, AsyncGenerator, Callable
|
12
|
+
from typing import Dict, List, Tuple, Optional, cast, AsyncGenerator, Callable, Literal
|
13
13
|
from itertools import count
|
14
14
|
from aioquic.asyncio.client import connect
|
15
15
|
from aioquic.asyncio.protocol import QuicConnectionProtocol
|
@@ -66,8 +66,6 @@ class GNExceptions:
|
|
66
66
|
class data(Exception):
|
67
67
|
def __init__(self, message="Ошибка подключения к серверу dns.core Сервер не подтвердил подключение."):
|
68
68
|
super().__init__(message)
|
69
|
-
|
70
|
-
|
71
69
|
|
72
70
|
class connector():
|
73
71
|
"""Ошибка подключения к серверу <?>~connector.gn"""
|
@@ -84,6 +82,22 @@ class GNExceptions:
|
|
84
82
|
def __init__(self, message="Ошибка подключения к серверу <?>~connector.gn. Сервер не подтвердил подключение."):
|
85
83
|
super().__init__(message)
|
86
84
|
|
85
|
+
class client():
|
86
|
+
"""Ошибка клиента"""
|
87
|
+
|
88
|
+
class connection(Exception):
|
89
|
+
def __init__(self, message="Ошибка подключения к серверу. Сервер не найден."):
|
90
|
+
super().__init__(message)
|
91
|
+
|
92
|
+
class timeout(Exception):
|
93
|
+
def __init__(self, message="Ошибка подключения к серверу. Проблема с сетью или сервер перегружен"):
|
94
|
+
super().__init__(message)
|
95
|
+
|
96
|
+
class data(Exception):
|
97
|
+
def __init__(self, message="Ошибка подключения к серверу. Сервер не подтвердил подключение."):
|
98
|
+
super().__init__(message)
|
99
|
+
|
100
|
+
|
87
101
|
|
88
102
|
|
89
103
|
from KeyisBClient.gn import GNRequest, GNResponse, GNProtocol
|
@@ -101,13 +115,11 @@ class AsyncClient:
|
|
101
115
|
self.__request_callbacks = {}
|
102
116
|
self.__response_callbacks = {}
|
103
117
|
|
104
|
-
self.
|
105
|
-
|
106
|
-
self._active_connections: Dict[str, Any] = {}
|
118
|
+
self._active_connections: Dict[str, QuicClient] = {}
|
107
119
|
|
108
120
|
async def _getCoreDNS(self, domain: str):
|
109
121
|
|
110
|
-
if domain.split('.')[-1].isdigit() and domain.split(':')[-1].isdigit():
|
122
|
+
if domain.split('.')[-1].split(':')[0].isdigit() and domain.split(':')[-1].isdigit():
|
111
123
|
r2_data = {
|
112
124
|
"ip": domain.split(':')[0],
|
113
125
|
"port": int(domain.split(':')[-1])
|
@@ -143,53 +155,72 @@ class AsyncClient:
|
|
143
155
|
self.__response_callbacks[name] = callback
|
144
156
|
|
145
157
|
|
146
|
-
async def connect(self, domain: str):
|
147
|
-
|
148
|
-
|
158
|
+
async def connect(self, domain: str, restart_connection: bool = False, reconnect_wait: float = 10) -> 'QuicClient':
|
159
|
+
print('Запрос подключения')
|
160
|
+
if not restart_connection and domain in self._active_connections:
|
161
|
+
c = self._active_connections[domain]
|
162
|
+
print(f'Подключение уже было [{c.status}]')
|
163
|
+
if c.status == 'connecting':
|
164
|
+
print('ждем поделючения')
|
165
|
+
try:
|
166
|
+
await asyncio.wait_for(c.connect_future, reconnect_wait)
|
167
|
+
print('дождались')
|
168
|
+
if c.status == 'active':
|
169
|
+
return c
|
170
|
+
elif c.status == 'connecting': # если очень дого подключаемся, то кидаем ошибку
|
171
|
+
await self.disconnect(domain)
|
172
|
+
raise GNExceptions.ConnectionError.client.timeout
|
173
|
+
elif c.status == 'disconnect':
|
174
|
+
raise GNExceptions.ConnectionError.client.connection
|
175
|
+
except:
|
176
|
+
print('Заново соеденяемся...')
|
177
|
+
await self.disconnect(domain)
|
178
|
+
|
179
|
+
else:
|
180
|
+
return c
|
181
|
+
|
182
|
+
c = QuicClient()
|
183
|
+
c.status = 'connecting'
|
184
|
+
self._active_connections[domain] = c
|
149
185
|
|
150
186
|
data = await self._getCoreDNS(domain)
|
151
|
-
# подключаемся к серверу gn-proxy
|
152
|
-
await self._client.connect(data['ip'], data['port'])
|
153
|
-
self._active_connections[domain] = 'active'
|
154
187
|
|
155
|
-
async def disconnect(self):
|
156
|
-
await self._client.disconnect()
|
157
188
|
|
158
189
|
|
159
|
-
|
160
|
-
|
190
|
+
def f(domain):
|
191
|
+
self._active_connections.pop(domain)
|
161
192
|
|
193
|
+
c._disconnect_signal = f
|
194
|
+
c._domain = domain
|
162
195
|
|
163
|
-
|
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
|
-
"""
|
196
|
+
await c.connect(data['ip'], data['port'])
|
197
|
+
await c.connect_future
|
175
198
|
|
199
|
+
return c
|
176
200
|
|
201
|
+
async def disconnect(self, domain):
|
202
|
+
if domain not in self._active_connections:
|
203
|
+
return
|
204
|
+
|
205
|
+
await self._active_connections[domain].disconnect()
|
177
206
|
|
178
207
|
|
208
|
+
def _return_token(self, bigToken: str, s: bool = True) -> str:
|
209
|
+
return bigToken[:128] if s else bigToken[128:]
|
179
210
|
|
180
211
|
|
212
|
+
async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]], restart_connection: bool = False, reconnect_wait: float = 10) -> GNResponse:
|
181
213
|
if isinstance(request, GNRequest):
|
182
214
|
|
183
215
|
|
184
|
-
|
185
|
-
await self.connect(request.url.hostname)
|
216
|
+
c = await self.connect(request.url.hostname, restart_connection, reconnect_wait)
|
186
217
|
|
187
218
|
|
188
219
|
|
189
220
|
for f in self.__request_callbacks.values():
|
190
221
|
asyncio.create_task(f(request))
|
191
222
|
|
192
|
-
r = await
|
223
|
+
r = await c.asyncRequest(request)
|
193
224
|
|
194
225
|
for f in self.__response_callbacks.values():
|
195
226
|
asyncio.create_task(f(r))
|
@@ -249,16 +280,6 @@ class AsyncClient:
|
|
249
280
|
|
250
281
|
yield response
|
251
282
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
# gn:quik
|
261
|
-
|
262
283
|
from aioquic.asyncio.protocol import QuicConnectionProtocol
|
263
284
|
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
|
264
285
|
from aioquic.quic.connection import END_STATES
|
@@ -266,18 +287,6 @@ import asyncio
|
|
266
287
|
from collections import deque
|
267
288
|
from typing import Dict, Deque, Tuple, Optional, List
|
268
289
|
|
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
290
|
import asyncio
|
282
291
|
import time
|
283
292
|
from collections import deque
|
@@ -286,11 +295,10 @@ from itertools import count
|
|
286
295
|
from typing import Deque, Dict, Optional, Tuple, Union
|
287
296
|
|
288
297
|
from aioquic.quic.configuration import QuicConfiguration
|
289
|
-
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
|
298
|
+
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset, ConnectionTerminated
|
290
299
|
|
291
300
|
|
292
301
|
class RawQuicClient(QuicConnectionProtocol):
|
293
|
-
"""Чистый‑QUIC клиент с приоритизированным SYS‑каналом + стриминг."""
|
294
302
|
|
295
303
|
SYS_RATIO_NUM = 9 # SYS 9/10
|
296
304
|
SYS_RATIO_DEN = 10
|
@@ -301,6 +309,8 @@ class RawQuicClient(QuicConnectionProtocol):
|
|
301
309
|
def __init__(self, *args, **kwargs):
|
302
310
|
super().__init__(*args, **kwargs)
|
303
311
|
|
312
|
+
self.quicClient: QuicClient = None
|
313
|
+
|
304
314
|
self._sys_stream_id: Optional[int] = None
|
305
315
|
self._queue_sys: Deque[Tuple[int, bytes, bool]] = deque()
|
306
316
|
self._queue_user: Deque[Tuple[int, bytes, bool]] = deque()
|
@@ -427,6 +437,17 @@ class RawQuicClient(QuicConnectionProtocol):
|
|
427
437
|
if not handler.done():
|
428
438
|
handler.set_exception(RuntimeError("stream reset"))
|
429
439
|
|
440
|
+
|
441
|
+
elif isinstance(event, ConnectionTerminated):
|
442
|
+
print("QUIC connection closed")
|
443
|
+
print("Error code:", event.error_code)
|
444
|
+
print("Reason:", event.reason_phrase)
|
445
|
+
if self.quicClient is None:
|
446
|
+
return
|
447
|
+
|
448
|
+
asyncio.create_task(self.quicClient.disconnect())
|
449
|
+
|
450
|
+
|
430
451
|
# ─────────────────────────────────────────── scheduler ─┤
|
431
452
|
def _enqueue(self, sid: int, blob: bytes, end_stream: bool, is_sys: bool):
|
432
453
|
(self._queue_sys if is_sys else self._queue_user).append((sid, blob, end_stream))
|
@@ -443,6 +464,7 @@ class RawQuicClient(QuicConnectionProtocol):
|
|
443
464
|
if q is None:
|
444
465
|
break
|
445
466
|
sid, blob, end_stream = q.popleft()
|
467
|
+
print(f'Отправка стрима {sid}')
|
446
468
|
self._quic.send_stream_data(sid, blob, end_stream=end_stream)
|
447
469
|
self.transmit()
|
448
470
|
self._activity()
|
@@ -563,10 +585,20 @@ class QuicClient:
|
|
563
585
|
def __init__(self):
|
564
586
|
self._quik_core: Optional[RawQuicClient] = None
|
565
587
|
self._client_cm = None
|
588
|
+
self._disconnect_signal = None
|
589
|
+
self._domain = None
|
590
|
+
|
591
|
+
self.status: Literal['active', 'connecting', 'disconnect']
|
592
|
+
|
593
|
+
self.connect_future = asyncio.get_event_loop().create_future()
|
594
|
+
self.connection_time: datetime.datetime = None
|
566
595
|
|
567
596
|
async def connect(self, ip: str, port: int):
|
597
|
+
self.status = 'connecting'
|
598
|
+
self.connection_time = datetime.datetime.now()
|
568
599
|
cfg = QuicConfiguration(is_client=True, alpn_protocols=["gn:backend"])
|
569
600
|
cfg.load_verify_locations(KeyisBClient.ssl_gw_crt_path)
|
601
|
+
cfg.idle_timeout = 40
|
570
602
|
|
571
603
|
self._client_cm = connect(
|
572
604
|
ip,
|
@@ -575,15 +607,48 @@ class QuicClient:
|
|
575
607
|
create_protocol=RawQuicClient,
|
576
608
|
wait_connected=True,
|
577
609
|
)
|
578
|
-
|
610
|
+
|
611
|
+
|
612
|
+
try:
|
613
|
+
self._quik_core = await self._client_cm.__aenter__()
|
614
|
+
self._quik_core.quicClient = self
|
615
|
+
|
616
|
+
self.status = 'active'
|
617
|
+
if not self.connect_future.done():
|
618
|
+
self.connect_future.set_result(True)
|
619
|
+
except Exception as e:
|
620
|
+
print(f'Error connecting: {e}')
|
621
|
+
if not self.connect_future.done():
|
622
|
+
self.connect_future.set_exception(GNExceptions.ConnectionError.client.connection)
|
623
|
+
await self._client_cm.__aexit__()
|
579
624
|
|
580
625
|
async def disconnect(self):
|
581
|
-
self.
|
582
|
-
|
583
|
-
self._quik_core
|
626
|
+
self.status = 'disconnect'
|
627
|
+
|
628
|
+
if self._quik_core is not None:
|
629
|
+
self._quik_core.stop()
|
630
|
+
|
631
|
+
|
632
|
+
if self._disconnect_signal is not None:
|
633
|
+
self._disconnect_signal(self._domain)
|
634
|
+
|
635
|
+
|
636
|
+
if self._quik_core is not None:
|
637
|
+
|
638
|
+
|
639
|
+
for fut in self._quik_core._inflight.values():
|
640
|
+
if isinstance(fut, asyncio.Queue):
|
641
|
+
del fut
|
642
|
+
else:
|
643
|
+
fut.set_exception(Exception)
|
644
|
+
|
645
|
+
for fut in self._quik_core._sys_inflight.values():
|
646
|
+
fut.set_exception(Exception)
|
647
|
+
|
584
648
|
|
585
|
-
|
586
|
-
|
649
|
+
self._quik_core.close()
|
650
|
+
await self._quik_core.wait_closed()
|
651
|
+
self._quik_core = None
|
587
652
|
|
588
653
|
async def asyncRequest(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]]) -> GNResponse:
|
589
654
|
if self._quik_core is None:
|
@@ -0,0 +1,9 @@
|
|
1
|
+
GNServer/___client.py,sha256=hmeUL2Vqp-BnwJeRcLZAaIfRNVxBrRRB_AFk9ofkei4,25459
|
2
|
+
GNServer/__init__.py,sha256=J4bjDqXVSEgOhD-FmkhpCeU4NJkp2BKMUKgltiuSXVo,1437
|
3
|
+
GNServer/_app.py,sha256=R8E099VO6qAMXqfLShw1HcchNfEJa2R8ZtYJkS4cJMs,25525
|
4
|
+
GNServer/_client.py,sha256=uM88y3yHBBH6qjG5L6bqFMCOVveTeyjyV-vlw1JOmG8,27397
|
5
|
+
gnserver-0.0.0.0.19.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
|
6
|
+
gnserver-0.0.0.0.19.dist-info/METADATA,sha256=95tWArZAgX6HibYLhdiQUQzf9MLDvLOCZuReAH3QkGw,805
|
7
|
+
gnserver-0.0.0.0.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
gnserver-0.0.0.0.19.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
|
9
|
+
gnserver-0.0.0.0.19.dist-info/RECORD,,
|
@@ -1,8 +0,0 @@
|
|
1
|
-
GNServer/__init__.py,sha256=J4bjDqXVSEgOhD-FmkhpCeU4NJkp2BKMUKgltiuSXVo,1437
|
2
|
-
GNServer/_app.py,sha256=tMNXDB10sUJHvVoTOc0KP3Mp3Dm6QyzZb3QfIgVIxR0,24736
|
3
|
-
GNServer/_client.py,sha256=tSpFkUuwHRPqmIjdTdcYWzPwDUTDaDRNhQOBTlL4jpg,24725
|
4
|
-
gnserver-0.0.0.0.17.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
|
5
|
-
gnserver-0.0.0.0.17.dist-info/METADATA,sha256=bFWTS0TNuCQIb7-zc8VarnDdzc99BNwJE-CSQYLI13g,805
|
6
|
-
gnserver-0.0.0.0.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
-
gnserver-0.0.0.0.17.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
|
8
|
-
gnserver-0.0.0.0.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|