GNServer 0.0.0.0.22__py3-none-any.whl → 0.0.0.0.24__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/_app.py +35 -5
- GNServer/_client.py +68 -31
- GNServer/tools/_models.py +33 -36
- {gnserver-0.0.0.0.22.dist-info → gnserver-0.0.0.0.24.dist-info}/METADATA +1 -1
- gnserver-0.0.0.0.24.dist-info/RECORD +11 -0
- gnserver-0.0.0.0.22.dist-info/RECORD +0 -11
- {gnserver-0.0.0.0.22.dist-info → gnserver-0.0.0.0.24.dist-info}/WHEEL +0 -0
- {gnserver-0.0.0.0.22.dist-info → gnserver-0.0.0.0.24.dist-info}/licenses/LICENSE +0 -0
- {gnserver-0.0.0.0.22.dist-info → gnserver-0.0.0.0.24.dist-info}/top_level.txt +0 -0
GNServer/_app.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
import re
|
5
|
+
import sys
|
5
6
|
import uuid
|
6
7
|
import anyio
|
7
8
|
import decimal
|
@@ -533,6 +534,7 @@ class App:
|
|
533
534
|
'async': inspect.iscoroutinefunction(fn),
|
534
535
|
'parameters': inspect.signature(fn).parameters
|
535
536
|
})
|
537
|
+
self._events[name] = events
|
536
538
|
|
537
539
|
return fn
|
538
540
|
return decorator
|
@@ -774,7 +776,7 @@ class App:
|
|
774
776
|
|
775
777
|
try:
|
776
778
|
|
777
|
-
response = await self._api.
|
779
|
+
response = await self._api.dispatchRequest(request)
|
778
780
|
|
779
781
|
if inspect.isasyncgen(response):
|
780
782
|
async for chunk in response: # type: ignore[misc]
|
@@ -796,7 +798,7 @@ class App:
|
|
796
798
|
logger.debug(f'Отправлен на сервер ответ -> {response.command} {response.payload if response.payload and len((response.payload)) < 200 else ''}')
|
797
799
|
self.transmit()
|
798
800
|
except Exception as e:
|
799
|
-
if e
|
801
|
+
if isinstance(e, _BaseEXception):
|
800
802
|
e: GNExceptions.UnprocessableEntity = e
|
801
803
|
r = e.assembly()
|
802
804
|
return r
|
@@ -824,11 +826,16 @@ class App:
|
|
824
826
|
cert_path: str,
|
825
827
|
key_path: str,
|
826
828
|
*,
|
827
|
-
host:
|
829
|
+
host: str = '0.0.0.0',
|
828
830
|
idle_timeout: float = 20.0,
|
829
831
|
wait: bool = True,
|
830
832
|
run: Optional[Callable] = None
|
831
833
|
):
|
834
|
+
"""
|
835
|
+
# Запустить сервер
|
836
|
+
|
837
|
+
Запускает сервер в главном процессе asyncio.run()
|
838
|
+
"""
|
832
839
|
|
833
840
|
self.domain = domain
|
834
841
|
|
@@ -842,7 +849,7 @@ class App:
|
|
842
849
|
|
843
850
|
async def _main():
|
844
851
|
|
845
|
-
await self.dispatchEvent('
|
852
|
+
await self.dispatchEvent('start')
|
846
853
|
|
847
854
|
await serve(
|
848
855
|
host,
|
@@ -855,7 +862,6 @@ class App:
|
|
855
862
|
if run is not None:
|
856
863
|
await run()
|
857
864
|
|
858
|
-
await self.dispatchEvent('run')
|
859
865
|
|
860
866
|
|
861
867
|
|
@@ -863,3 +869,27 @@ class App:
|
|
863
869
|
await asyncio.Event().wait()
|
864
870
|
|
865
871
|
asyncio.run(_main())
|
872
|
+
|
873
|
+
|
874
|
+
def runByVMHost(self):
|
875
|
+
"""
|
876
|
+
# Запусить через VM-host
|
877
|
+
|
878
|
+
Заупскает сервер через процесс vm-host
|
879
|
+
"""
|
880
|
+
argv = sys.argv[1:]
|
881
|
+
command = argv[0]
|
882
|
+
if command == 'gn:vm-host:start':
|
883
|
+
domain = argv[1]
|
884
|
+
port = int(argv[2])
|
885
|
+
cert_path = argv[3]
|
886
|
+
key_path = argv[4]
|
887
|
+
host = argv[5]
|
888
|
+
|
889
|
+
self.run(
|
890
|
+
domain=domain,
|
891
|
+
port=port,
|
892
|
+
cert_path=cert_path,
|
893
|
+
key_path=key_path,
|
894
|
+
host=host
|
895
|
+
)
|
GNServer/_client.py
CHANGED
@@ -53,7 +53,7 @@ class GNExceptions:
|
|
53
53
|
def __init__(self, message="Ошибка подключения к серверу openconnector.gn. Сервер не подтвердил подключение."):
|
54
54
|
super().__init__(message)
|
55
55
|
|
56
|
-
class
|
56
|
+
class dns():
|
57
57
|
"""Ошибка подключения к серверу dns.core"""
|
58
58
|
class connection(Exception):
|
59
59
|
def __init__(self, message="Ошибка подключения к серверу dns.core Сервер не найден."):
|
@@ -100,7 +100,7 @@ class GNExceptions:
|
|
100
100
|
|
101
101
|
|
102
102
|
|
103
|
-
from KeyisBClient.gn import GNRequest, GNResponse
|
103
|
+
from KeyisBClient.gn import GNRequest, GNResponse
|
104
104
|
|
105
105
|
|
106
106
|
|
@@ -108,7 +108,7 @@ from KeyisBClient.gn import GNRequest, GNResponse, GNProtocol
|
|
108
108
|
class AsyncClient:
|
109
109
|
def __init__(self):
|
110
110
|
self.__dns_core__ipv4 = '51.250.85.38:52943'
|
111
|
-
self.__dns_gn__ipv4 = None
|
111
|
+
self.__dns_gn__ipv4: Optional[str] = None
|
112
112
|
|
113
113
|
self.__current_session = {}
|
114
114
|
self.__request_callbacks = {}
|
@@ -116,36 +116,69 @@ class AsyncClient:
|
|
116
116
|
|
117
117
|
self._active_connections: Dict[str, QuicClient] = {}
|
118
118
|
|
119
|
-
|
119
|
+
self.__dns_client: Optional[AsyncClient] = None
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
return r2_data
|
121
|
+
|
122
|
+
async def getCoreDNS(self, domain: str) -> str:
|
123
|
+
|
124
|
+
if ':' in domain and domain.split('.')[-1].split(':')[0].isdigit() and domain.split(':')[-1].isdigit():
|
125
|
+
return domain
|
127
126
|
|
128
127
|
try:
|
129
|
-
if self.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
128
|
+
if self.__dns_client is None:
|
129
|
+
self.__dns_client = AsyncClient()
|
130
|
+
|
131
|
+
r1 = await self.__dns_client.request(GNRequest('GET', Url(f'gn://{self.__dns_core__ipv4}/getIp?d={domain}')), keep_alive=False)
|
132
|
+
|
133
|
+
if r1.command != 'ok':
|
134
|
+
raise GNExceptions.ConnectionError.dns.data
|
135
135
|
|
136
|
+
r1_data = r1.payload
|
137
|
+
|
138
|
+
return r1_data['ip'] + ':' + str(r1_data['port'])
|
136
139
|
|
137
|
-
r2 = await httpxAsyncClient.request('GET', f'https://{self.__dns_gn__ipv4}/gn/getIp?d={domain}')
|
138
140
|
except httpx.TimeoutException:
|
139
|
-
raise GNExceptions.ConnectionError.
|
141
|
+
raise GNExceptions.ConnectionError.dns.timeout
|
140
142
|
except:
|
141
|
-
raise GNExceptions.ConnectionError.
|
143
|
+
raise GNExceptions.ConnectionError.dns.connection
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
async def getGNDNS(self, domain: str, token: Optional[str] = None) -> str:
|
148
|
+
|
149
|
+
if ':' in domain and domain.split('.')[-1].split(':')[0].isdigit() and domain.split(':')[-1].isdigit():
|
150
|
+
return domain
|
151
|
+
|
152
|
+
|
153
|
+
if self.__dns_gn__ipv4 is None:
|
154
|
+
self.__dns_gn__ipv4 = await self.getCoreDNS('dns.gn')
|
155
|
+
|
156
|
+
|
157
|
+
try:
|
158
|
+
if self.__dns_client is None:
|
159
|
+
self.__dns_client = AsyncClient()
|
160
|
+
|
161
|
+
if token is not None:
|
162
|
+
payload = {'token': token}
|
163
|
+
else:
|
164
|
+
payload = None
|
165
|
+
|
166
|
+
r1 = await self.__dns_client.request(GNRequest('GET', Url(f'gn://{self.__dns_gn__ipv4}/getIp?d={domain}'), payload=payload))
|
167
|
+
|
168
|
+
if r1.command != 'ok':
|
169
|
+
raise GNExceptions.ConnectionError.dns.data
|
170
|
+
|
171
|
+
r1_data = r1.payload
|
172
|
+
|
173
|
+
return r1_data['ip'] + ':' + str(r1_data['port'])
|
174
|
+
|
175
|
+
except httpx.TimeoutException:
|
176
|
+
raise GNExceptions.ConnectionError.dns.timeout
|
177
|
+
except:
|
178
|
+
raise GNExceptions.ConnectionError.dns.connection
|
142
179
|
|
143
|
-
if r2.status_code != 200:
|
144
|
-
raise GNExceptions.ConnectionError.dns_core.data
|
145
180
|
|
146
|
-
r2_data = r2.json()
|
147
181
|
|
148
|
-
return r2_data
|
149
182
|
|
150
183
|
def addRequestCallback(self, callback: Callable, name: str):
|
151
184
|
self.__request_callbacks[name] = callback
|
@@ -154,7 +187,7 @@ class AsyncClient:
|
|
154
187
|
self.__response_callbacks[name] = callback
|
155
188
|
|
156
189
|
|
157
|
-
async def connect(self, domain: str, restart_connection: bool = False, reconnect_wait: float = 10) -> 'QuicClient':
|
190
|
+
async def connect(self, domain: str, restart_connection: bool = False, reconnect_wait: float = 10, keep_alive: bool = True) -> 'QuicClient':
|
158
191
|
print('Запрос подключения')
|
159
192
|
if not restart_connection and domain in self._active_connections:
|
160
193
|
c = self._active_connections[domain]
|
@@ -182,7 +215,9 @@ class AsyncClient:
|
|
182
215
|
c.status = 'connecting'
|
183
216
|
self._active_connections[domain] = c
|
184
217
|
|
185
|
-
data = await self.
|
218
|
+
data = await self.getCoreDNS(domain)
|
219
|
+
|
220
|
+
data = data.split(':')
|
186
221
|
|
187
222
|
|
188
223
|
|
@@ -192,7 +227,7 @@ class AsyncClient:
|
|
192
227
|
c._disconnect_signal = f
|
193
228
|
c._domain = domain
|
194
229
|
|
195
|
-
await c.connect(data['ip'], data['port'])
|
230
|
+
await c.connect(data['ip'], data['port'], keep_alive=keep_alive)
|
196
231
|
await c.connect_future
|
197
232
|
|
198
233
|
return c
|
@@ -208,11 +243,11 @@ class AsyncClient:
|
|
208
243
|
return bigToken[:128] if s else bigToken[128:]
|
209
244
|
|
210
245
|
|
211
|
-
async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]], restart_connection: bool = False, reconnect_wait: float = 10) -> GNResponse:
|
246
|
+
async def request(self, request: Union[GNRequest, AsyncGenerator[GNRequest, Any]], keep_alive: bool = True, restart_connection: bool = False, reconnect_wait: float = 10) -> GNResponse:
|
212
247
|
if isinstance(request, GNRequest):
|
213
248
|
|
214
249
|
|
215
|
-
c = await self.connect(request.url.hostname, restart_connection, reconnect_wait)
|
250
|
+
c = await self.connect(request.url.hostname, restart_connection, reconnect_wait, keep_alive=keep_alive)
|
216
251
|
|
217
252
|
|
218
253
|
|
@@ -326,7 +361,6 @@ class RawQuicClient(QuicConnectionProtocol):
|
|
326
361
|
self._last_activity = time.time()
|
327
362
|
self._running = True
|
328
363
|
self._ping_id_gen = count(1) # int64 ping‑id generator
|
329
|
-
asyncio.create_task(self._keepalive_loop())
|
330
364
|
|
331
365
|
# ───────────────────────────────────────── private helpers ─┤
|
332
366
|
def _activity(self):
|
@@ -592,7 +626,7 @@ class QuicClient:
|
|
592
626
|
self.connect_future = asyncio.get_event_loop().create_future()
|
593
627
|
self.connection_time: datetime.datetime = None
|
594
628
|
|
595
|
-
async def connect(self, ip: str, port: int):
|
629
|
+
async def connect(self, ip: str, port: int, keep_alive: bool = True):
|
596
630
|
self.status = 'connecting'
|
597
631
|
self.connection_time = datetime.datetime.now()
|
598
632
|
cfg = QuicConfiguration(is_client=True, alpn_protocols=["gn:backend"])
|
@@ -612,6 +646,9 @@ class QuicClient:
|
|
612
646
|
self._quik_core = await self._client_cm.__aenter__()
|
613
647
|
self._quik_core.quicClient = self
|
614
648
|
|
649
|
+
if keep_alive:
|
650
|
+
asyncio.create_task(self._quik_core._keepalive_loop())
|
651
|
+
|
615
652
|
self.status = 'active'
|
616
653
|
if not self.connect_future.done():
|
617
654
|
self.connect_future.set_result(True)
|
@@ -619,7 +656,7 @@ class QuicClient:
|
|
619
656
|
print(f'Error connecting: {e}')
|
620
657
|
if not self.connect_future.done():
|
621
658
|
self.connect_future.set_exception(GNExceptions.ConnectionError.client.connection)
|
622
|
-
await self._client_cm.__aexit__()
|
659
|
+
await self._client_cm.__aexit__(None, None, None)
|
623
660
|
|
624
661
|
async def disconnect(self):
|
625
662
|
self.status = 'disconnect'
|
GNServer/tools/_models.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import time
|
2
|
-
import
|
2
|
+
import asyncio
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
|
3
6
|
|
4
7
|
class TTLDict:
|
5
8
|
def __init__(self, default_ttl: int = 60, cleanup_interval: int = 300):
|
@@ -7,46 +10,38 @@ class TTLDict:
|
|
7
10
|
:param default_ttl: TTL по умолчанию (сек), если при записи не указан
|
8
11
|
:param cleanup_interval: периодическая очистка от просроченных ключей (сек), по умолчанию 5 мин
|
9
12
|
"""
|
13
|
+
|
10
14
|
self._store = {}
|
11
|
-
self._lock = threading.Lock()
|
12
15
|
self._default_ttl = default_ttl
|
13
16
|
self._cleanup_interval = cleanup_interval
|
14
|
-
self.
|
15
|
-
|
16
|
-
self._timer = threading.Thread(target=self._cleanup_worker, daemon=True)
|
17
|
-
self._timer.start()
|
17
|
+
self._task = None
|
18
18
|
|
19
|
-
def set(self, key, value, ttl: int = None):
|
20
|
-
"""Установить значение с временем жизни (в секундах)."""
|
19
|
+
def set(self, key, value, ttl: Optional[int] = None):
|
21
20
|
if ttl is None:
|
22
21
|
ttl = self._default_ttl
|
23
22
|
expire_at = time.monotonic() + ttl
|
24
|
-
|
25
|
-
|
23
|
+
self._store[key] = (value, expire_at)
|
24
|
+
|
25
|
+
if self._task is None:
|
26
|
+
loop = asyncio.get_running_loop()
|
27
|
+
self._task = loop.create_task(self._cleanup_worker())
|
26
28
|
|
27
29
|
def get(self, key):
|
28
|
-
"""Получить значение или None, если ключ отсутствует/просрочен."""
|
29
30
|
now = time.monotonic()
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
return None
|
34
|
-
|
35
|
-
value, expire_at = item
|
36
|
-
if expire_at < now:
|
37
|
-
del self._store[key]
|
38
|
-
return None
|
31
|
+
item = self._store.get(key)
|
32
|
+
if not item:
|
33
|
+
return None
|
39
34
|
|
40
|
-
|
35
|
+
value, expire_at = item
|
36
|
+
if expire_at < now:
|
37
|
+
del self._store[key]
|
38
|
+
return None
|
39
|
+
return value
|
41
40
|
|
42
41
|
def __setitem__(self, key, value):
|
43
|
-
"""
|
44
|
-
cache[key] = val → TTL по умолчанию
|
45
|
-
"""
|
46
42
|
self.set(key, value, self._default_ttl)
|
47
43
|
|
48
44
|
def __getitem__(self, key):
|
49
|
-
"""cache[key] -> value | None"""
|
50
45
|
return self.get(key)
|
51
46
|
|
52
47
|
def __contains__(self, key):
|
@@ -58,20 +53,22 @@ class TTLDict:
|
|
58
53
|
def __repr__(self):
|
59
54
|
return f"<TTLDict size={len(self._store)}>"
|
60
55
|
|
61
|
-
def _cleanup_worker(self):
|
62
|
-
|
63
|
-
|
56
|
+
async def _cleanup_worker(self):
|
57
|
+
while True:
|
58
|
+
await asyncio.sleep(self._cleanup_interval)
|
64
59
|
self.cleanup()
|
65
60
|
|
66
61
|
def cleanup(self):
|
67
|
-
"""Удалить все просроченные
|
62
|
+
"""Удалить все просроченные ключи"""
|
68
63
|
now = time.monotonic()
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
self._store.pop(k, None)
|
64
|
+
expired = [k for k, (_, exp) in self._store.items() if exp < now]
|
65
|
+
for k in expired:
|
66
|
+
self._store.pop(k, None)
|
73
67
|
|
74
|
-
def stop(self):
|
68
|
+
async def stop(self):
|
75
69
|
"""Остановить фон очистки"""
|
76
|
-
self.
|
77
|
-
|
70
|
+
self._task.cancel()
|
71
|
+
try:
|
72
|
+
await self._task
|
73
|
+
except asyncio.CancelledError:
|
74
|
+
pass
|
@@ -0,0 +1,11 @@
|
|
1
|
+
GNServer/___client.py,sha256=hmeUL2Vqp-BnwJeRcLZAaIfRNVxBrRRB_AFk9ofkei4,25459
|
2
|
+
GNServer/__init__.py,sha256=V50sMYrrPdOGuI1iJm-SW7izhX-eggDH16AHvtIKjmM,1480
|
3
|
+
GNServer/_app.py,sha256=WYK16u-G6TDEMBa3MVaJP3-HzTh4NIcmzsyOdxaSsm0,30664
|
4
|
+
GNServer/_client.py,sha256=9zw3DbkVHjNr-DNhlLV92r2sNJ-VY9Ahc4QjP1sUCII,28443
|
5
|
+
GNServer/tools/__init__.py,sha256=itqkS5iBB2GEHqz8H-htqgd55rUi6utnuKVAzBBByCM,28
|
6
|
+
GNServer/tools/_models.py,sha256=1V94cbNHQGAl5l9DJbGvvkm1gmsgTEMgkjzlnZk8ymw,2264
|
7
|
+
gnserver-0.0.0.0.24.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
|
8
|
+
gnserver-0.0.0.0.24.dist-info/METADATA,sha256=kVi7DeXW2P6Zgk8y3ZcUuDNFiS_gRvrsR8exkZPJ3vM,805
|
9
|
+
gnserver-0.0.0.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
gnserver-0.0.0.0.24.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
|
11
|
+
gnserver-0.0.0.0.24.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
GNServer/___client.py,sha256=hmeUL2Vqp-BnwJeRcLZAaIfRNVxBrRRB_AFk9ofkei4,25459
|
2
|
-
GNServer/__init__.py,sha256=V50sMYrrPdOGuI1iJm-SW7izhX-eggDH16AHvtIKjmM,1480
|
3
|
-
GNServer/_app.py,sha256=Mtpuoj7CPjedW9m5wL6YzfAwCV9F-5a69ag2Dh2VwIk,29908
|
4
|
-
GNServer/_client.py,sha256=lwFGsNR2RullSNCuu7WXXdYfUbsV1_ZweMv-N-5Gpv0,27371
|
5
|
-
GNServer/tools/__init__.py,sha256=itqkS5iBB2GEHqz8H-htqgd55rUi6utnuKVAzBBByCM,28
|
6
|
-
GNServer/tools/_models.py,sha256=tXWR255ASJLlpdDrgMXcmRqD_nIcCb4qKeESvAhxWpU,2742
|
7
|
-
gnserver-0.0.0.0.22.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
|
8
|
-
gnserver-0.0.0.0.22.dist-info/METADATA,sha256=b0GuFIDOkuPhFhVj_Cm66zZsjpm1nvQLvzLjGZhZhFA,805
|
9
|
-
gnserver-0.0.0.0.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
-
gnserver-0.0.0.0.22.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
|
11
|
-
gnserver-0.0.0.0.22.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|