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 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.dispatch(request)
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.__qualname__.startswith("GNExceptions."):
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: Optional[str] = '0.0.0.0',
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('before-run')
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 dns_core():
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, GNProtocol
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
- async def _getCoreDNS(self, domain: str):
119
+ self.__dns_client: Optional[AsyncClient] = None
120
120
 
121
- if domain.split('.')[-1].split(':')[0].isdigit() and domain.split(':')[-1].isdigit():
122
- r2_data = {
123
- "ip": domain.split(':')[0],
124
- "port": int(domain.split(':')[-1])
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.__dns_gn__ipv4 is None:
130
- r1 = await httpxAsyncClient.request('GET', f'https://{self.__dns_core__ipv4}/gn/getIp?d=dns.gn')
131
- if r1.status_code != 200:
132
- raise GNExceptions.ConnectionError.dns_core.data
133
- r1_data = r1.json()
134
- self.__dns_gn__ipv4 = r1_data['ip'] + ':' + str(r1_data['port'])
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.dns_core.timeout
141
+ raise GNExceptions.ConnectionError.dns.timeout
140
142
  except:
141
- raise GNExceptions.ConnectionError.dns_core.connection
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._getCoreDNS(domain)
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 threading
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._stop_event = threading.Event()
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
- with self._lock:
25
- self._store[key] = (value, expire_at)
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
- with self._lock:
31
- item = self._store.get(key)
32
- if not item:
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
- return value
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
- while not self._stop_event.wait(self._cleanup_interval):
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
- with self._lock:
70
- expired = [k for k, (_, exp) in self._store.items() if exp < now]
71
- for k in expired:
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._stop_event.set()
77
- self._timer.join(timeout=1)
70
+ self._task.cancel()
71
+ try:
72
+ await self._task
73
+ except asyncio.CancelledError:
74
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.22
3
+ Version: 0.0.0.0.24
4
4
  Summary: GNServer
5
5
  Home-page: https://github.com/KeyisB/libs/tree/main/GNServer
6
6
  Author: KeyisB
@@ -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,,