25 Commits

Author SHA1 Message Date
a7a9f367c5 0.2.1 -> 0.2.2 2023-07-14 19:17:21 +03:00
8af4e6527f Update TODOs 2023-07-14 19:14:51 +03:00
7bda3dce29 core.py:
Change default cid to -1 for auth;
    Change client logger;
    Update logic of client.kick();
    Remove "kostil" :)));
    Add client._split_load().....(5h);
    DO SendFile - sending mods.;
    Move sync_resources back;
    Add client.ready for blocking "alive" info;
    Refactor client.remove_me();
    Add name for web Thread;
    Minor fixes;
tcp_server.py:
    From auth always return client;
    Add events call in auth;
    Refactor set_down_rw;
    Minor fixes;
2023-07-14 19:13:13 +03:00
6afe62b68e Move sync_resources logic before insert to clients. 2023-07-13 18:08:47 +03:00
dd2c461581 Update TODOs 2023-07-13 17:55:13 +03:00
a8c153691c 0.2.0 -> 0.2.1 2023-07-13 17:50:15 +03:00
52893513d0 Prepare for sync_resources;
Fix async bugs;
Recreate ID system;
Add Ss (Player counter) code;
2023-07-13 17:49:23 +03:00
1f595db700 Handle return from callback 2023-07-13 16:49:19 +03:00
565750e784 Refactor console logger 2023-07-13 16:44:05 +03:00
13321fb9b5 Minor update 2023-07-13 13:29:37 +03:00
cdf226ac5c Update TODOs 2023-07-13 11:41:00 +03:00
dcafef918a Minor naming update 2023-07-13 09:53:57 +03:00
6e46af4c13 Prepare for Upload mods 2023-07-13 02:35:38 +03:00
d21798aaf1 Minor fix 2023-07-13 02:34:56 +03:00
22105b2030 Minor fix 2023-07-13 02:34:25 +03:00
19c121f208 Refactor Client ID 2023-07-13 02:33:45 +03:00
85c379bd9e Minor fixes 2023-07-13 01:17:01 +03:00
a15eb316bb Update TODOs 2023-07-13 01:16:10 +03:00
cecd6f13d6 Handle FastApi Log in file 2023-07-13 00:44:51 +03:00
df171aaa70 Minor fix 2023-07-13 00:31:02 +03:00
5a1cb8a133 Handle web logs 2023-07-13 00:23:57 +03:00
d44cff1116 Update version 2023-07-12 23:58:35 +03:00
bc6cf60099 Update TODOs 2023-07-12 23:58:23 +03:00
fc886ef415 Create log history 2023-07-12 23:58:14 +03:00
bd7b988b01 logs 2023-07-12 23:39:52 +03:00
13 changed files with 557 additions and 282 deletions

1
.gitignore vendored
View File

@@ -137,3 +137,4 @@ dmypy.json
/src/plugins /src/plugins
/test/ /test/
*test.py *test.py
logs/

View File

@@ -11,17 +11,21 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Private access (Without key, Direct connect) - [x] Private access (Without key, Direct connect)
- [x] Public access (With key, listing in Launcher) - [x] Public access (With key, listing in Launcher)
- [X] Player authentication - [X] Player authentication
- [ ] KuiToi System
- [ ] Servers counter
- [ ] Players counter
- [ ] Etc.
- [ ] TCP Server part: - [ ] TCP Server part:
- [x] Handle code - [x] Handle code
- [x] Understanding BeamMP header - [x] Understanding BeamMP header
- [ ] Upload mods - [x] Upload mods
- [x] Connecting to the world - [x] Connecting to the world
- [x] Chat - [x] Chat
- [ ] Player counter _(Code: Ss)_ - [x] Players online counter
- [ ] Car state synchronizations _(Codes: We, Vi)_ - [ ] Car state synchronizations _(Codes: We, Vi)_
- [ ] "ABG:" (compressed data) - [ ] "ABG:" (compressed data)
- [x] Decompress data - [x] Decompress data
- [ ] Vehicle data - [ ] Vehicle data _(Code: Os)_
- [ ] UDP Server part: - [ ] UDP Server part:
- [ ] Players synchronizations _(Code: Zp)_ - [ ] Players synchronizations _(Code: Zp)_
- [ ] Ping _(Code: p)_ - [ ] Ping _(Code: p)_
@@ -29,7 +33,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Logger - [x] Logger
- [x] Just logging - [x] Just logging
- [x] Log in file - [x] Log in file
- [ ] Lig history (.1.log, .2.log, ...) - [x] Log history (.1.log, .2.log, ...)
- [x] Console: - [x] Console:
- [x] Tabulation - [x] Tabulation
- [ ] _(Deferred)_ Static text (bug) - [ ] _(Deferred)_ Static text (bug)
@@ -46,8 +50,9 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] WebAPI - [x] WebAPI
- [x] HTTP API Server (fastapi) - [x] HTTP API Server (fastapi)
- [x] Stop and Start with core - [x] Stop and Start with core
- [x] Custom logger - [x] Configure FastAPI logger
- [ ] Sync with event system - [ ] Sync with event system
- [ ] Add methods...
- [ ] [Documentation](docs/en/readme.md) - [ ] [Documentation](docs/en/readme.md)
## Installation ## Installation

View File

@@ -2,7 +2,7 @@
# File core.__init__.py # File core.__init__.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.3 # Version 1.3
# Core version: 0.2.0 # Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai) # Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
@@ -10,8 +10,8 @@
__title__ = 'KuiToi-Server' __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.2.0' __version__ = '0.2.2'
__build__ = 776 __build__ = 1113 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"

View File

@@ -1,11 +1,13 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.py # File core.core.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.2.0 # Version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
import math
import os import os
import random
import zlib import zlib
from threading import Thread from threading import Thread
@@ -23,20 +25,22 @@ class Client:
def __init__(self, reader, writer, core): def __init__(self, reader, writer, core):
self.reader = reader self.reader = reader
self.writer = writer self.writer = writer
self.down_rw = (None, None)
self.log = utils.get_logger("client(None:0)") self.log = utils.get_logger("client(None:0)")
self.addr = writer.get_extra_info("sockname") self.addr = writer.get_extra_info("sockname")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.cid = 0 self.cid = -1
self.key = None self.key = None
self.nick = None self.nick = None
self.roles = None self.roles = None
self.guest = True self.guest = True
self.alive = True self.alive = True
self.ready = False
def _update_logger(self): def _update_logger(self):
self.log = utils.get_logger(f"{self.nick}:{self.cid})")
self.log.debug(f"Update logger") self.log.debug(f"Update logger")
self.log = utils.get_logger(f"client({self.nick}:{self.cid})")
def is_disconnected(self): def is_disconnected(self):
if not self.alive: if not self.alive:
@@ -52,13 +56,15 @@ class Client:
return False return False
async def kick(self, reason): async def kick(self, reason):
self.log.info(f"Client: \"IP: {self.addr!r}; ID: {self.cid}\" - kicked with reason: \"{reason}\"") if not self.alive:
self.log.debug(f"Kick({reason}) skipped;")
return
self.log.info(f"Kicked with reason: \"{reason}\"")
await self.tcp_send(b"K" + bytes(reason, "utf-8")) await self.tcp_send(b"K" + bytes(reason, "utf-8"))
# self.writer.close()
# await self.writer.wait_closed()
self.alive = False self.alive = False
# await self.remove_me()
async def tcp_send(self, data): async def tcp_send(self, data, to_all=False, writer=None):
# TNetwork.cpp; Line: 383 # TNetwork.cpp; Line: 383
# BeamMP TCP protocol sends a header of 4 bytes, followed by the data. # BeamMP TCP protocol sends a header of 4 bytes, followed by the data.
@@ -66,18 +72,30 @@ class Client:
# ^------^^---...-^ # ^------^^---...-^
# size data # size data
self.log.debug(f"tcp_send({data})") if writer is None:
writer = self.writer
if to_all:
for client in self.Core.clients:
if not client:
continue
await client.tcp_send(data)
return
# self.log.debug(f"tcp_send({data})")
if len(data) == 10: if len(data) == 10:
data += b"." data += b"."
header = len(data).to_bytes(4, "little", signed=True) header = len(data).to_bytes(4, "little", signed=True)
self.log.debug(f'len(data) {len(data)}; send {header + data}') self.log.debug(f'len: {len(data)}; send: {header + data}')
self.writer.write(header + data) try:
await self.writer.drain() writer.write(header + data)
await writer.drain()
except ConnectionError:
self.log.debug('tcp_send: Disconnected')
self.alive = False
async def recv(self): async def recv(self):
# if not self.is_disconnected(): try:
# self.log.debug(f"Client with {self.nick}({self.cid}) disconnected")
# return b""
header = await self.reader.read(4) # header: 4 bytes header = await self.reader.read(4) # header: 4 bytes
int_header = 0 int_header = 0
@@ -85,16 +103,20 @@ class Client:
int_header += header[i] int_header += header[i]
if int_header <= 0: if int_header <= 0:
await asyncio.sleep(0.1)
self.is_disconnected()
if self.alive:
self.log.debug(f"Header: {header}")
await self.kick("Invalid packet - header negative") await self.kick("Invalid packet - header negative")
return b"" return b""
if int_header > 100 * MB: if int_header > 100 * MB:
await self.kick("Header size limit exceeded") await self.kick("Header size limit exceeded")
self.log.warn(f"Client {self.nick}({self.cid}) sent header of >100MB - " self.log.warn(f"Client {self.nick}:{self.cid} sent header of >100MB - "
f"assuming malicious intent and disconnecting the client.") f"assuming malicious intent and disconnecting the client.")
return b"" return b""
data = await self.reader.read(101 * MB) data = await self.reader.read(100 * MB)
self.log.debug(f"header: `{header}`; int_header: `{int_header}`; data: `{data}`;") self.log.debug(f"header: `{header}`; int_header: `{int_header}`; data: `{data}`;")
if len(data) != int_header: if len(data) != int_header:
@@ -106,38 +128,171 @@ class Client:
self.log.debug(f"ABG: {data}") self.log.debug(f"ABG: {data}")
return data return data
return data return data
except ConnectionError:
self.alive = False
return b""
async def _split_load(self, start, end, d_sock, filename):
real_size = end - start
writer = self.down_rw[1] if d_sock else self.writer
who = 'dwn' if d_sock else 'srv'
self.log.debug(f"[{who}] Real size: {real_size/MB}mb; {real_size == end}, {real_size*2 == end}")
with open(filename, 'rb') as f:
f.seek(start)
data = f.read(end)
try:
writer.write(data)
await writer.drain()
self.log.debug(f"[{who}] File sent.")
except ConnectionError:
self.alive = False
self.log.debug(f"[{who}] Disconnected.")
# break
return real_size
# chunk_size = 125 * MB
# if chunk_size > real_size:
# chunk_size = real_size
# chunks = math.floor(real_size / chunk_size)
# self.log.debug(f"[{who}] s:{start}, e:{end}, c:{chunks}, cz:{chunk_size/MB}mb, rs:{real_size/MB}mb")
# dw = 0
# for chunk in range(1, chunks + 1):
# chunk_end = start + (chunk_size * chunk)
# chunk_start = chunk_end - chunk_size
# # if chunk_start != 0:
# # chunk_start -= 1
# real_size -= chunk_size
# if chunk_size > real_size:
# chunk_end = real_size
# self.log.debug(f"[{who}] Chunk: {chunk}; Start: {chunk_start}; End: {chunk_end/MB};")
# with open(filename, 'rb') as f:
# f.seek(chunk_start)
# data = f.read(chunk_end)
# try:
# writer.write(data)
# await writer.drain()
# except ConnectionError:
# self.alive = False
# self.log.debug(f"[{who}] Disconnected")
# break
# dw += len(data)
# del data
# self.log.debug(f"[{who}] File sent.")
# return dw
async def sync_resources(self): async def sync_resources(self):
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) while self.alive:
data = await self.recv() data = await self.recv()
if data.startswith(b"SR"): self.log.debug(f"data: {data!r}")
await self.tcp_send(b"-") # Cannot handle mods for now. if data.startswith(b"f"):
data = await self.recv() file = data[1:].decode("utf-8")
if data == b"Done": self.log.debug(f"Sending File: {file}")
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") size = -1
await self.last_handle() for mod in self.Core.mods_list:
if type(mod) == int:
continue
if mod.get('path') == file:
size = mod['size']
self.log.debug("File is accept.")
break
if size == -1:
await self.tcp_send(b"CO")
await self.kick(f"Not allowed mod: " + file)
return
await self.tcp_send(b"AG")
t = 0
while not self.down_rw[0]:
await asyncio.sleep(0.1)
t += 1
if t > 50:
await self.kick("Missing download socket")
return
self.log.info(f"Requested mode: {file!r}")
self.log.debug(f"Mode size: {size/MB}")
async def last_handle(self): msize = math.floor(size / 2)
# self.is_disconnected() # uploads = [
self.log.debug(f"Alive: {self.alive}") # asyncio.create_task(self._split_load(0, msize, False, file)), # SplitLoad_0
# asyncio.create_task(self._split_load(msize, size, True, file)) # SplitLoad_1
# ]
# await asyncio.wait(uploads)
uploads = [
self._split_load(0, msize, False, file),
self._split_load(msize, size, True, file)
]
sl0, sl1 = await asyncio.gather(*uploads)
sent = sl0 + sl1
ok = sent == size
lost = size - sent
self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}")
self.log.debug(f"SplitLoad_0: {sl0/MB}mb; "
f"SplitLoad_1: {sl1/MB}MB; At all ({ok}): Sent: {sent/MB}mb; Lost: {lost/MB}mb")
if not ok:
self.alive = False
self.log.error(f"Error while sending.")
return
elif data.startswith(b"SR"):
path_list = ''
size_list = ''
for mod in self.Core.mods_list:
if type(mod) == int:
continue
path_list += f"{mod['path']};"
size_list += f"{mod['size']};"
mod_list = path_list + size_list
self.log.debug(f"Mods List: {mod_list}")
if len(mod_list) == 0:
await self.tcp_send(b"-")
else:
await self.tcp_send(bytes(mod_list, "utf-8"))
elif data == b"Done":
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
break
return
async def looper(self):
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID
await self.sync_resources()
while self.alive: while self.alive:
data = await self.recv() data = await self.recv()
if data == b"": if data == b"":
if not self.alive: if not self.alive:
break break
elif self.is_disconnected():
break
else: else:
await asyncio.sleep(.2)
self.is_disconnected()
continue continue
code = data.decode()[0] code = data.decode()[0]
self.log.debug(f"Code: {code}, data: {data}") self.log.debug(f"Received code: {code}, data: {data}")
match code: match code:
case "H": case "H":
# Client connected # Client connected
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8")) self.ready = True
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8"), to_all=True)
case "C": case "C":
# Chat # Chat
await self.tcp_send(data) await self.tcp_send(data, to_all=True)
async def remove_me(self):
await asyncio.sleep(0.3)
self.alive = False
if (self.cid > 0 or self.nick is not None) and \
self.Core.clients_by_nick.get(self.nick):
# if self.ready:
# await self.tcp_send(b"", to_all=True) # I'm disconnected.
self.log.debug(f"Removing client {self.nick}:{self.cid}")
self.log.info("Disconnected")
self.Core.clients[self.cid] = None
self.Core.clients_by_id.pop(self.cid)
self.Core.clients_by_nick.pop(self.nick)
else:
self.log.debug(f"Removing client; Closing connection...")
if not self.writer.is_closing():
self.writer.close()
_, down_w = self.down_rw
if down_w and not down_w.is_closing():
down_w.close()
class Core: class Core:
@@ -147,8 +302,9 @@ class Core:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.run = False self.run = False
self.direct = False self.direct = False
self.clients = {} self.clients = []
self.clients_counter = 0 self.clients_by_id = {}
self.clients_by_nick = {}
self.mods_dir = "./mods" self.mods_dir = "./mods"
self.mods_list = [0, ] self.mods_list = [0, ]
self.server_ip = config.Server["server_ip"] self.server_ip = config.Server["server_ip"]
@@ -162,35 +318,67 @@ class Core:
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BeamMP_version = "3.2.0" self.BeamMP_version = "3.2.0"
def get_client(self, sock=None, cid=None): def get_client(self, cid=None, nick=None):
if cid: if cid is not None:
return self.clients.get(cid) return self.clients_by_id.get(cid)
if sock: if nick:
return self.clients.get(sock.getsockname()) return self.clients_by_nick.get(nick)
def insert_client(self, client): async def insert_client(self, client):
self.log.debug(f"Inserting client: {client.cid}") await asyncio.sleep(random.randint(3, 9) * 0.01)
self.clients.update({client.cid: client, client.nick: client}) cid = 0
for _client in self.clients:
if not _client:
break
if _client.cid == cid:
cid += 1
else:
break
await asyncio.sleep(random.randint(3, 9) * 0.01)
if not self.clients[cid]:
client.cid = cid
self.clients_by_nick.update({client.nick: client})
self.log.debug(f"Inserting client: {client.nick}:{client.cid}")
self.clients_by_id.update({client.cid: client})
self.clients[client.cid] = client
# noinspection PyProtectedMember
client._update_logger()
return
await self.insert_client(client)
def create_client(self, *args, **kwargs): def create_client(self, *args, **kwargs):
client = Client(*args, **kwargs) self.log.debug(f"Create client")
self.clients_counter += 1 client = Client(core=self, *args, **kwargs)
client.id = self.clients_counter
client._update_logger()
self.log.debug(f"Create client: {client.cid}; clients_counter: {self.clients_counter}")
return client return client
def get_clients_list(self, need_cid=False):
out = ""
for client in self.clients:
if not client:
continue
out += f"{client.nick}"
if need_cid:
out += f":{client.cid}"
out += ","
if out:
out = out[:-1]
return out
async def check_alive(self): async def check_alive(self):
await asyncio.sleep(5) maxp = config.Game['players']
self.log.debug(f"Checking if clients is alive") while self.run:
for cl in self.clients.values(): await asyncio.sleep(1)
d = await cl.is_disconnected() ca = f"Ss{len(self.clients_by_id)}/{maxp}:{self.get_clients_list()}"
if d: for client in self.clients:
self.log.debug(f"Client ID: {cl.id} died...") if not client:
continue
if not client.ready:
client.is_disconnected()
continue
await client.tcp_send(bytes(ca, "utf-8"))
@staticmethod @staticmethod
def start_web(): def start_web():
global uvserver
uvconfig = uvicorn.Config("modules.WebAPISystem.app:web_app", uvconfig = uvicorn.Config("modules.WebAPISystem.app:web_app",
host=config.WebAPI["server_ip"], host=config.WebAPI["server_ip"],
port=config.WebAPI["server_port"], port=config.WebAPI["server_port"],
@@ -199,14 +387,14 @@ class Core:
webapp.uvserver = uvserver webapp.uvserver = uvserver
uvserver.run() uvserver.run()
@staticmethod async def stop_me(self):
async def stop_me():
while webapp.data_run[0]: while webapp.data_run[0]:
await asyncio.sleep(1) await asyncio.sleep(1)
self.run = False
raise KeyboardInterrupt raise KeyboardInterrupt
# noinspection SpellCheckingInspection,PyPep8Naming # noinspection SpellCheckingInspection,PyPep8Naming
async def authenticate(self, test=False): async def heartbeat(self, test=False):
if config.Auth["private"] or self.direct: if config.Auth["private"] or self.direct:
if test: if test:
self.log.info(f"Server runnig in Direct connect mode.") self.log.info(f"Server runnig in Direct connect mode.")
@@ -222,6 +410,7 @@ class Core:
modstotalsize = self.mods_list[0] modstotalsize = self.mods_list[0]
modstotal = len(self.mods_list) - 1 modstotal = len(self.mods_list) - 1
while self.run: while self.run:
try:
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"], data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json", "port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
"private": config.Auth['private'], "version": self.BeamMP_version, "private": config.Auth['private'], "version": self.BeamMP_version,
@@ -233,7 +422,6 @@ class Core:
# Sentry? # Sentry?
ok = False ok = False
body = {} body = {}
code = 0
for server_url in BEAM_backend: for server_url in BEAM_backend:
url = "https://" + server_url + "/heartbeat" url = "https://" + server_url + "/heartbeat"
try: try:
@@ -280,19 +468,28 @@ class Core:
return ok return ok
await asyncio.sleep(5) await asyncio.sleep(5)
except Exception as e:
self.log.error(f"Error in heartbeat: {e}")
async def main(self): async def main(self):
self.run = True self.run = True
self.tcp = self.tcp(self, self.server_ip, self.server_port) self.tcp = self.tcp(self, self.server_ip, self.server_port)
self.udp = self.udp(self, self.server_ip, self.server_port) self.udp = self.udp(self, self.server_ip, self.server_port)
console.add_command(
"list",
lambda x: f"Players list: {self.get_clients_list(True)}"
)
try: try:
# WebApi Start # WebApi Start
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:
self.log.debug("Initializing WebAPI...") self.log.debug("Initializing WebAPI...")
web_thread = Thread(target=self.start_web) web_thread = Thread(target=self.start_web, name="WebApiThread")
web_thread.start() web_thread.start()
self.log.debug(f"WebAPI started at new thread: {web_thread.name}")
self.web_thread = web_thread self.web_thread = web_thread
# noinspection PyProtectedMember
self.web_stop = webapp._stop self.web_stop = webapp._stop
await asyncio.sleep(.3)
# Mods handler # Mods handler
self.log.debug("Listing mods..") self.log.debug("Listing mods..")
@@ -309,10 +506,12 @@ class Core:
if lmods > 0: if lmods > 0:
self.log.info(f"Loaded {lmods} mods: {round(self.mods_list[0] / MB, 2)}mb") self.log.info(f"Loaded {lmods} mods: {round(self.mods_list[0] / MB, 2)}mb")
await self.authenticate(True) await self.heartbeat(True)
for i in range(int(config.Game["players"] * 1.3)):
self.clients.append(None)
tasks = [] tasks = []
# self.check_alive() # self.udp.start,
nrtasks = [self.tcp.start, self.udp.start, console.start, self.stop_me, self.authenticate, ] nrtasks = [self.tcp.start, console.start, self.stop_me, self.heartbeat, self.check_alive]
for task in nrtasks: for task in nrtasks:
tasks.append(asyncio.create_task(task())) tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
@@ -328,7 +527,7 @@ class Core:
pass pass
finally: finally:
self.tcp.stop() self.tcp.stop()
self.udp.stop() # self.udp.stop()
self.run = False self.run = False
def start(self): def start(self):
@@ -337,5 +536,6 @@ class Core:
def stop(self): def stop(self):
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
if config.WebAPI["enabled"]:
asyncio.run(self.web_stop()) asyncio.run(self.web_stop())
exit(0) exit(0)

View File

@@ -1,13 +1,13 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.pyi # File core.core.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.2.0 # Version 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
from asyncio import StreamWriter, StreamReader from asyncio import StreamWriter, StreamReader
from threading import Thread from threading import Thread
from typing import Callable from typing import Callable, List, Dict, Tuple
from core import utils from core import utils
from .tcp_server import TCPServer from .tcp_server import TCPServer
@@ -19,24 +19,27 @@ class Client:
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client": def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
self.reader = reader self.reader = reader
self.writer = writer self.writer = writer
self.down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
self.log = utils.get_logger("client(id: )") self.log = utils.get_logger("client(id: )")
self.addr = writer.get_extra_info("sockname") self.addr = writer.get_extra_info("sockname")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.cid = 0 self.cid: int = -1
self.key: str = None self.key: str = None
self.nick: str = None self.nick: str = None
self.roles: str = None self.roles: str = None
self.guest = True self.guest = True
self.alive = True self.alive = True
self.ready = False
def is_disconnected(self) -> bool: ... def is_disconnected(self) -> bool: ...
async def kick(self, reason: str) -> None: ... async def kick(self, reason: str) -> None: ...
async def tcp_send(self, data: bytes) -> None: ... async def tcp_send(self, data: bytes, to_all:bool = False, writer: StreamWriter = None) -> None: ...
async def sync_resources(self) -> None: ... async def sync_resources(self) -> None: ...
async def recv(self) -> bytes: ... async def recv(self) -> bytes: ...
async def last_handle(self) -> bytes: ... async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ...
async def looper(self) -> None: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...
async def remove_me(self) -> None: ...
class Core: class Core:
def __init__(self): def __init__(self):
@@ -44,7 +47,9 @@ class Core:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.run = False self.run = False
self.direct = False self.direct = False
self.clients = dict() self.clients: List[Client | None]= []
self.clients_by_id: Dict[{int: Client}]= {}
self.clients_by_nick: Dict[{str: Client}] = {}
self.clients_counter: int = 0 self.clients_counter: int = 0
self.mods_dir: str = "mods" self.mods_dir: str = "mods"
self.mods_list: list = [] self.mods_list: list = []
@@ -56,14 +61,15 @@ class Core:
self.web_stop: Callable = lambda: None self.web_stop: Callable = lambda: None
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BeamMP_version = "3.2.0" self.BeamMP_version = "3.2.0"
def insert_client(self, client: Client) -> None: ... def get_client(self, cid=None, nick=None) -> Client | None: ...
async def insert_client(self, client: Client) -> None: ...
def create_client(self, *args, **kwargs) -> Client: ... def create_client(self, *args, **kwargs) -> Client: ...
def get_clients_list(self, need_cid=False) -> str: ...
async def check_alive(self) -> None: ... async def check_alive(self) -> None: ...
@staticmethod @staticmethod
def start_web() -> None: ... def start_web() -> None: ...
@staticmethod def stop_me(self) -> None: ...
def stop_me() -> None: ... async def heartbeat(self, test=False) -> None: ...
async def authenticate(self, test=False) -> None: ...
async def main(self) -> None: ... async def main(self) -> None: ...
def start(self) -> None: ... def start(self) -> None: ...
def stop(self) -> None: ... def stop(self) -> None: ...

View File

@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.2.0 # Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -15,84 +15,97 @@ from core import utils
class TCPServer: class TCPServer:
def __init__(self, core, host, port): def __init__(self, core, host, port):
self.log = utils.get_logger("TCPServer") self.log = utils.get_logger("TCPServer")
self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.host = host self.host = host
self.port = port self.port = port
self.loop = asyncio.get_event_loop() self.run = False
async def auth_client(self, reader, writer): async def auth_client(self, reader, writer):
client = self.Core.create_client(reader, writer) client = self.Core.create_client(reader, writer)
self.log.info(f"Identifying new ClientConnection...") self.log.info(f"Identifying new ClientConnection...")
data = await client.recv() data = await client.recv()
self.log.debug(f"recv1 data: {data}") self.log.debug(f"Version: {data}")
if len(data) > 50: if data.decode("utf-8") != f"VC{self.Core.client_major_version}":
await client.kick("Too long data")
return False, None
if "VC2.0" not in data.decode("utf-8"):
await client.kick("Outdated Version.") await client.kick("Outdated Version.")
return False, None return False, client
else: else:
await client.tcp_send(b"S") # Accepted client version await client.tcp_send(b"S") # Accepted client version
data = await client.recv() data = await client.recv()
self.log.debug(f"recv2 data: {data}") self.log.debug(f"Key: {data}")
if len(data) > 50: if len(data) > 50:
await client.kick("Invalid Key (too long)!") await client.kick("Invalid Key (too long)!")
return False, None return False, client
client.key = data.decode("utf-8") client.key = data.decode("utf-8")
ev.call_event("auth_sent_key", client)
try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
url = 'https://auth.beammp.com/pkToUser' url = 'https://auth.beammp.com/pkToUser'
async with session.post(url, data={'key': client.key}) as response: async with session.post(url, data={'key': client.key}) as response:
res = await response.json() res = await response.json()
self.log.debug(f"res: {res}") self.log.debug(f"res: {res}")
try:
if res.get("error"): if res.get("error"):
await client.kick('Invalid key! Please restart your game.') await client.kick('Invalid key! Please restart your game.')
return return False, client
client.nick = res["username"] client.nick = res["username"]
client.roles = res["roles"] client.roles = res["roles"]
client.guest = res["guest"] client.guest = res["guest"]
# noinspection PyProtectedMember
client._update_logger() client._update_logger()
except Exception as e: except Exception as e:
self.log.error(f"Auth error: {e}") self.log.error(f"Auth error: {e}")
await client.kick('Invalid authentication data! Try to connect in 5 minutes.') await client.kick('Invalid authentication data! Try to reconnect in 5 minutes.')
return False, client
# TODO: Password party for _client in self.Core.clients:
# await client.tcp_send(b"S") # Ask client key (How?) if not _client:
continue
if _client.nick == client.nick and _client.guest == client.guest:
await client.kick('Stale Client (replaced by new client)')
return False, client
ev.call_event("on_auth", client) ev.call_event("auth_ok", client)
if len(self.Core.clients) > config.Game["players"]: if len(self.Core.clients_by_id) > config.Game["players"]:
await client.kick("Server full!") await client.kick("Server full!")
return False, client
else: else:
self.log.info("Identification success") self.log.info("Identification success")
self.Core.insert_client(client) await self.Core.insert_client(client)
return True, client return True, client
async def handle_download(self, writer): async def set_down_rw(self, reader, writer):
# TODO: HandleDownload try:
self.log.debug(f"Client: \"IP: {0!r}; ID: {0}\" - HandleDownload!") cid = (await reader.read(1))[0]
return False client = self.Core.get_client(cid=cid)
if client:
client.down_rw = (reader, writer)
self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!")
else:
writer.close()
self.log.debug(f"Unknown client id:{cid} - HandleDownload")
finally:
return
async def handle_code(self, code, reader, writer): async def handle_code(self, code, reader, writer):
match code: match code:
case "C": case "C":
result, client = await self.auth_client(reader, writer) result, client = await self.auth_client(reader, writer)
if result: if result:
await client.sync_resources() await client.looper()
# await client.kick("Authentication success! Server not ready.") return result, client
return True
return False
case "D": case "D":
return await self.handle_download(writer) await self.set_down_rw(reader, writer)
case "P": case "P":
writer.write(b"P") writer.write(b"P")
await writer.drain() await writer.drain()
return True writer.close()
case _: case _:
self.log.error(f"Unknown code: {code}") self.log.error(f"Unknown code: {code}")
return False writer.close()
return False, None
async def handle_client(self, reader, writer): async def handle_client(self, reader, writer):
while True: while True:
@@ -102,28 +115,38 @@ class TCPServer:
break break
code = data.decode() code = data.decode()
self.log.debug(f"Received {code!r} from {writer.get_extra_info('sockname')!r}") self.log.debug(f"Received {code!r} from {writer.get_extra_info('sockname')!r}")
result = await self.handle_code(code, reader, writer) # task = asyncio.create_task(self.handle_code(code, reader, writer))
if not result: # await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
_, cl = await self.handle_code(code, reader, writer)
if cl:
await cl.remove_me()
del cl
break break
except Exception as e: except Exception as e:
self.log.error("Error while connecting..") self.log.error("Error while connecting..")
self.log.error(f"Error: {e}") self.log.exception(e)
traceback.print_exc() traceback.print_exc()
break break
async def start(self): async def start(self):
self.log.debug("Starting TCP server.") self.log.debug("Starting TCP server.")
self.run = True
try: try:
server = await asyncio.start_server(self.handle_client, self.host, self.port, server = await asyncio.start_server(self.handle_client, self.host, self.port,
backlog=config.Game["players"] + 1) backlog=int(config.Game["players"] * 1.3))
except OSError as e:
self.log.error(f"Error: {e}")
self.Core.run = False
raise e
self.log.debug(f"TCP server started on {server.sockets[0].getsockname()!r}") self.log.debug(f"TCP server started on {server.sockets[0].getsockname()!r}")
while True: while True:
async with server: async with server:
await server.serve_forever() await server.serve_forever()
except OSError as e:
self.log.error("Cannot bind port")
raise e
except BaseException as e:
self.log.error(f"Error: {e}")
raise e
finally:
self.run = False
self.Core.run = False
def stop(self): def stop(self):
self.log.debug("Stopping TCP server") self.log.debug("Stopping TCP server")

View File

@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.pyi # File core.tcp_server.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.2.0 # Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -15,13 +15,14 @@ from core.core import Client
class TCPServer: class TCPServer:
def __init__(self, core: Core, host, port): def __init__(self, core: Core, host, port):
self.log = utils.get_logger("TCPServer") self.log = utils.get_logger("TCPServer")
self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.host = host self.host = host
self.port = port self.port = port
self.loop = asyncio.get_event_loop() self.run = False
async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ... async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ...
async def handle_download(self, writer: StreamWriter) -> bool: ... async def set_down_rw(self, reader: StreamReader, writer: StreamWriter) -> bool: ...
async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> bool: ... async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ...
async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ... async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ...
async def start(self) -> None: ... async def start(self) -> None: ...
async def stop(self) -> None: ... async def stop(self) -> None: ...

View File

@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.0 # Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -14,43 +14,47 @@ class UDPServer:
def __init__(self, core, host, port): def __init__(self, core, host, port):
self.log = utils.get_logger("UDPServer") self.log = utils.get_logger("UDPServer")
self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.host = host self.host = host
self.port = port self.port = port
self.loop = asyncio.get_event_loop() self.run = False
async def handle_client(self, srv_sock): async def handle_client(self, reader, writer):
while True: while True:
try: try:
data, addr = await self.loop.sock_recv(srv_sock, 1024) data = await reader.read(1)
if not data: if not data:
break break
code = data.decode() code = data.decode()
self.log.debug(f"Received {code!r} from {addr!r}") self.log.debug(f"Received {code!r} from {writer.get_extra_info('sockname')!r}")
# if not await self.handle_code(code, sock): # await self.handle_code(code, reader, writer)
# break # task = asyncio.create_task(self.handle_code(code, reader, writer))
# await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
if not writer.is_closing():
writer.close()
self.log.debug("Disconnected.")
break
except Exception as e: except Exception as e:
self.log.error("Error while connecting..")
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
traceback.print_exc() traceback.print_exc()
break break
srv_sock.close()
self.log.error("Error while connecting..")
async def start(self): async def start(self):
self.log.debug("Starting UDP server.")
self.run = True
try:
pass pass
# self.log.debug("Starting UDP server.") except OSError as e:
# await self.stop() self.log.error("Cannot bind port or other error")
# srv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) raise e
# srv_sock.bind((self.host, self.port)) except BaseException as e:
# self.log.debug(f"Serving on {srv_sock.getsockname()}") self.log.error(f"Error: {e}")
# try: raise e
# await self.handle_client(srv_sock) finally:
# except Exception as e: self.run = False
# self.log.error(f"Error: {e}") self.Core.run = False
# traceback.print_exc()
# finally:
# await self.stop()
def stop(self): def stop(self):
pass self.log.debug("Stopping UDP server")
# self.log.debug("Stopping UDP server")

View File

@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.0 # Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -13,10 +13,11 @@ class UDPServer:
def __init__(self, core, host, port): def __init__(self, core, host, port):
self.log = utils.get_logger("UDPServer") self.log = utils.get_logger("UDPServer")
self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.host = host self.host = host
self.port = port self.port = port
self.loop = asyncio.get_event_loop() self.run = False
async def handle_client(self, srv_sock) -> None: ... async def handle_client(self, srv_sock) -> None: ...
async def start(self) -> None: ... async def start(self) -> None: ...

View File

@@ -1,21 +1,38 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.utils.py # File core.utils.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.0 # Version 1.1
# Core version: 0.2.2
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import datetime
import logging import logging
import os
import tarfile
log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s" log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s"
log_format_access = '[%(asctime)s | %(name)-14s | %(levelname)-5s] %(client_addr)s - "%(request_line)s" %(status_code)s' log_dir = "./logs/"
log_file = "server.log" log_file = log_dir + "server.log"
log_level = logging.INFO log_level = logging.INFO
# Инициализируем логирование # Инициализируем логирование
logging.basicConfig(level=log_level, format=log_format) logging.basicConfig(level=log_level, format=log_format)
# Настройка логирование в файл. # Настройка логирование в файл.
# if os.path.exists(log_file): if not os.path.exists(log_dir):
# os.remove(log_file) os.mkdir(log_dir)
if os.path.exists(log_file):
mtime = os.path.getmtime(log_file)
gz_path = log_dir + datetime.datetime.fromtimestamp(mtime).strftime('%d.%m.%Y') + "-%s.tar.gz"
index = 1
while True:
if not os.path.exists(gz_path % index):
break
index += 1
with tarfile.open(gz_path % index, "w:gz") as tar:
logs_files = [log_file, "./logs/web.log", "./logs/web_access.log"]
for file in logs_files:
if os.path.exists(file):
tar.add(file, os.path.basename(file))
os.remove(file)
fh = logging.FileHandler(log_file, encoding='utf-8') fh = logging.FileHandler(log_file, encoding='utf-8')
fh.setFormatter(logging.Formatter(log_format)) fh.setFormatter(logging.Formatter(log_format))

View File

@@ -14,6 +14,7 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import NestedCompleter from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.history import FileHistory from prompt_toolkit.history import FileHistory
from prompt_toolkit.patch_stdout import patch_stdout
from core import get_logger from core import get_logger
@@ -25,6 +26,7 @@ class Console:
prompt_out="", prompt_out="",
not_found="Command \"%s\" not found in alias.", not_found="Command \"%s\" not found in alias.",
debug=False) -> None: debug=False) -> None:
self.__logger = get_logger("console")
self.__is_run = False self.__is_run = False
self.__prompt_in = prompt_in self.__prompt_in = prompt_in
self.__prompt_out = prompt_out self.__prompt_out = prompt_out
@@ -43,10 +45,11 @@ class Console:
self.completer = NestedCompleter.from_nested_dict(self.__alias) self.completer = NestedCompleter.from_nested_dict(self.__alias)
def __debug(self, *x): def __debug(self, *x):
if self.__is_debug: self.__logger.debug(f"{x}")
x = list(x) # if self.__is_debug:
x.insert(0, "\r CONSOLE DEBUG:") # x = list(x)
self.__print(*x) # x.insert(0, "\r CONSOLE DEBUG:")
# self.__print(*x)
def __getitem__(self, item): def __getitem__(self, item):
print(item) print(item)
@@ -131,7 +134,8 @@ class Console:
print_formatted_text(s) print_formatted_text(s)
def log(self, s: AnyStr) -> None: def log(self, s: AnyStr) -> None:
self.write(s) self.__logger.info(f"{s}")
# self.write(s)
def __lshift__(self, s: AnyStr) -> None: def __lshift__(self, s: AnyStr) -> None:
self.write(s) self.write(s)
@@ -186,22 +190,29 @@ class Console:
session = PromptSession(history=FileHistory('./.cmdhistory')) session = PromptSession(history=FileHistory('./.cmdhistory'))
while True: while True:
try: try:
cmd_in = await session.prompt_async(self.__prompt_in, with patch_stdout():
completer=self.completer, auto_suggest=AutoSuggestFromHistory()) cmd_in = await session.prompt_async(
self.__prompt_in,
completer=self.completer,
auto_suggest=AutoSuggestFromHistory()
)
cmd_s = cmd_in.split(" ") cmd_s = cmd_in.split(" ")
cmd = cmd_s[0] cmd = cmd_s[0]
if cmd == "": if cmd == "":
pass continue
else: else:
command_object = self.__func.get(cmd) command_object = self.__func.get(cmd)
if command_object: if command_object:
self.log(str(command_object['f'](cmd_s[1:]))) out = command_object['f'](cmd_s[1:])
if out:
self.log(out)
else: else:
self.log(self.__not_found % cmd) self.log(self.__not_found % cmd)
except KeyboardInterrupt: except KeyboardInterrupt:
raise KeyboardInterrupt raise KeyboardInterrupt
except Exception as e: except Exception as e:
print(f"Error in console.py: {e}") print(f"Error in console.py: {e}")
self.__logger.exception(e)
async def start(self): async def start(self):
self.__is_run = True self.__is_run = True
@@ -210,13 +221,3 @@ class Console:
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
self.__is_run = False self.__is_run = False
raise KeyboardInterrupt raise KeyboardInterrupt
# if __name__ == '__main__':
# c = Console()
# c.logger_hook()
# c.builtins_hook()
# log = logging.getLogger(name="name")
# log.info("Starting console")
# print("Starting console")
# asyncio.run(c.start())

View File

@@ -7,7 +7,6 @@ from fastapi.exceptions import RequestValidationError
from starlette import status from starlette import status
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from uvicorn.config import LOGGING_CONFIG
import core.utils import core.utils
from . import utils from . import utils
@@ -21,30 +20,6 @@ uvserver = None
data_pool = [] data_pool = []
data_run = [True] data_run = [True]
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format_access
LOGGING_CONFIG["formatters"].update({
"file_default": {
"fmt": core.utils.log_format
},
"file_access": {
"fmt": core.utils.log_format_access
}
})
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
LOGGING_CONFIG["handlers"].update({
"file_default": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "webserver.log"
},
"file_access": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "webserver.log"
}
})
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None): def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None):
if 200 >= code <= 300: if 200 >= code <= 300:
@@ -78,6 +53,7 @@ async def _method(method, secret_key: str = None):
async def _stop(): async def _stop():
await asyncio.sleep(1) await asyncio.sleep(1)
if uvserver is not None:
uvserver.should_exit = True uvserver.should_exit = True
data_run[0] = False data_run[0] = False

View File

@@ -2,10 +2,17 @@ import asyncio
import sys import sys
import click import click
from uvicorn.server import Server, logger import uvicorn.server as uvs
from uvicorn.config import LOGGING_CONFIG
from uvicorn.lifespan import on from uvicorn.lifespan import on
import core.utils
# logger = core.utils.get_logger("uvicorn")
# uvs.logger = logger
logger = uvs.logger
def ev_log_started_message(self, listeners) -> None: def ev_log_started_message(self, listeners) -> None:
cfg = self.config cfg = self.config
@@ -42,7 +49,7 @@ async def ev_shutdown(self, sockets=None) -> None:
try: try:
await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown) await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown)
except asyncio.TimeoutError: except asyncio.TimeoutError:
logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded",len(self.server_state.tasks)) logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded", len(self.server_state.tasks))
for t in self.server_state.tasks: for t in self.server_state.tasks:
if sys.version_info < (3, 9): if sys.version_info < (3, 9):
t.cancel() t.cancel()
@@ -81,7 +88,40 @@ async def on_shutdown(self) -> None:
def hack_fastapi(): def hack_fastapi():
Server.shutdown = ev_shutdown uvs.Server.shutdown = ev_shutdown
Server._log_started_message = ev_log_started_message uvs.Server._log_started_message = ev_log_started_message
on.LifespanOn.startup = on_startup on.LifespanOn.startup = on_startup
on.LifespanOn.shutdown = on_shutdown on.LifespanOn.shutdown = on_shutdown
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format
LOGGING_CONFIG["formatters"].update({
"file_default": {
"()": "logging.Formatter",
"fmt": core.utils.log_format
},
"file_access": {
"()": "logging.Formatter",
"fmt": core.utils.log_format
}
})
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
LOGGING_CONFIG["handlers"].update({
"file_default": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "./logs/web.log",
"encoding": "utf-8",
"formatter": "file_default"
},
"file_access": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "./logs/web_access.log",
"encoding": "utf-8",
"formatter": "file_access"
}
})
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
print(LOGGING_CONFIG)