mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2026-02-16 02:20:52 +00:00
[-] WebAPI
This commit is contained in:
@@ -9,8 +9,8 @@
|
||||
__title__ = 'KuiToi-Server'
|
||||
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
||||
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
||||
__version__ = '0.4.7'
|
||||
__build__ = 2473 # Я это считаю лог файлами
|
||||
__version__ = '0.4.8 (pre)'
|
||||
__build__ = 2474 # Я это считаю лог файлами
|
||||
__author__ = 'SantaSpeen'
|
||||
__author_email__ = 'admin@kuitoi.su'
|
||||
__license__ = "FPA"
|
||||
@@ -91,7 +91,7 @@ if not config.Auth['private'] and not config.Auth['key']:
|
||||
text=i18n.GUI_enter_key_message,
|
||||
ok_text=i18n.GUI_ok,
|
||||
cancel_text=i18n.GUI_cancel).run()
|
||||
config_provider.save()
|
||||
config_provider.save()
|
||||
if not config.Auth['private'] and not config.Auth['key']:
|
||||
log.error(i18n.auth_empty_key)
|
||||
log.info(i18n.stop)
|
||||
|
||||
@@ -9,17 +9,14 @@ import math
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
import aiohttp
|
||||
import uvicorn
|
||||
|
||||
from core import utils, __version__
|
||||
from core.Client import Client
|
||||
from core.tcp_server import TCPServer
|
||||
from core.udp_server import UDPServer
|
||||
from modules import PluginsLoader
|
||||
from modules.WebAPISystem import app as webapp
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@@ -41,9 +38,6 @@ class Core:
|
||||
self.server_port = config.Server["server_port"]
|
||||
self.tcp = TCPServer
|
||||
self.udp = UDPServer
|
||||
self.web_thread = None
|
||||
self.web_pool = webapp.data_pool
|
||||
self.web_stop = None
|
||||
|
||||
self.lock_upload = False
|
||||
|
||||
@@ -129,24 +123,6 @@ class Core:
|
||||
continue
|
||||
await client.kick("Server shutdown!")
|
||||
|
||||
@staticmethod
|
||||
def start_web():
|
||||
uvconfig = uvicorn.Config("modules.WebAPISystem.app:web_app",
|
||||
host=config.WebAPI["server_ip"],
|
||||
port=config.WebAPI["server_port"],
|
||||
loop="asyncio")
|
||||
uvserver = uvicorn.Server(uvconfig)
|
||||
webapp.uvserver = uvserver
|
||||
uvserver.run()
|
||||
|
||||
async def stop_me(self):
|
||||
if not config.WebAPI['enabled']:
|
||||
return
|
||||
while webapp.data_run[0]:
|
||||
await asyncio.sleep(1)
|
||||
self.run = False
|
||||
raise KeyboardInterrupt
|
||||
|
||||
# noinspection SpellCheckingInspection,PyPep8Naming
|
||||
async def heartbeat(self, test=False):
|
||||
try:
|
||||
@@ -282,17 +258,6 @@ class Core:
|
||||
lpl.load()
|
||||
|
||||
try:
|
||||
# WebApi Start
|
||||
if config.WebAPI["enabled"]:
|
||||
self.log.debug("Initializing WebAPI...")
|
||||
web_thread = Thread(target=self.start_web, name="WebApiThread")
|
||||
web_thread.start()
|
||||
self.log.debug(f"WebAPI started at new thread: {web_thread.name}")
|
||||
self.web_thread = web_thread
|
||||
# noinspection PyProtectedMember
|
||||
self.web_stop = webapp._stop
|
||||
await asyncio.sleep(.3)
|
||||
|
||||
# Mods handler
|
||||
self.log.debug("Listing mods..")
|
||||
if not os.path.exists(self.mods_dir):
|
||||
@@ -313,8 +278,8 @@ class Core:
|
||||
for i in range(int(config.Game["players"] * 2.3)): # * 2.3 For down sock and buffer.
|
||||
self.clients.append(None)
|
||||
tasks = []
|
||||
# self.udp.start,
|
||||
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
|
||||
# self.udp.start
|
||||
f_tasks = [self.tcp.start, self.udp._start, console.start, self.heartbeat, self.check_alive]
|
||||
if config.RCON['enabled']:
|
||||
console.rcon.version = f"KuiToi {__version__}"
|
||||
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
|
||||
@@ -353,8 +318,6 @@ class Core:
|
||||
ev.call_event("_lua_plugins_unload")
|
||||
await ev.call_async_event("_plugins_unload")
|
||||
self.run = False
|
||||
if config.WebAPI["enabled"]:
|
||||
asyncio.run(self.web_stop())
|
||||
total_time = time.monotonic() - self.start_time
|
||||
hours = int(total_time // 3600)
|
||||
minutes = int((total_time % 3600) // 60)
|
||||
|
||||
@@ -13,7 +13,7 @@ import yaml
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
|
||||
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None):
|
||||
self.Auth = auth or {"key": None, "private": True}
|
||||
self.Game = game or {"map": "gridmap_v2", "players": 8, "cars": 1}
|
||||
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", "tags": "Freroam",
|
||||
@@ -22,12 +22,10 @@ class Config:
|
||||
"use_lua": False, "log_chat": True}
|
||||
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
|
||||
"password": secrets.token_hex(6)}
|
||||
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
|
||||
"access_token": secrets.token_hex(16)}
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}(Auth={self.Auth!r}, Game={self.Game!r}, Server={self.Server!r}, " \
|
||||
f"RCON={self.RCON!r}, Options={self.Options!r}, WebAPI={self.WebAPI!r})"
|
||||
return (f"{self.__class__.__name__}(Auth={self.Auth!r}, Game={self.Game!r}, Server={self.Server!r}, "
|
||||
f"RCON={self.RCON!r}, Options={self.Options!r})")
|
||||
|
||||
|
||||
class ConfigProvider:
|
||||
@@ -51,7 +49,7 @@ class ConfigProvider:
|
||||
if _again:
|
||||
print("Error: empty configuration.")
|
||||
exit(1)
|
||||
print("Empty config?..")
|
||||
print("Reconfig: empty configuration.")
|
||||
os.remove(self.config_path)
|
||||
self.config = Config()
|
||||
return self.read(True)
|
||||
@@ -68,5 +66,6 @@ class ConfigProvider:
|
||||
del _config.enc
|
||||
del _config.Options['debug']
|
||||
del _config.Options['encoding']
|
||||
os.remove(self.config_path)
|
||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(_config, f)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
from .app import web_app
|
||||
from .app import _stop
|
||||
@@ -1,105 +0,0 @@
|
||||
import asyncio
|
||||
from asyncio import CancelledError
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from starlette import status
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
import core.utils
|
||||
from . import utils
|
||||
|
||||
# from .models import SecretKey
|
||||
|
||||
web_app = FastAPI()
|
||||
log = core.utils.get_logger("web")
|
||||
|
||||
uvserver = None
|
||||
data_pool = []
|
||||
data_run = [True]
|
||||
|
||||
|
||||
def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None):
|
||||
if 200 >= code <= 300:
|
||||
return JSONResponse(content={"result": data, "error": None}, status_code=code)
|
||||
return JSONResponse(
|
||||
content={"error": {"code": error_code if error_code else code, "message": f"{error_message}"}, "result": None},
|
||||
status_code=code)
|
||||
|
||||
|
||||
@web_app.get("/")
|
||||
async def index():
|
||||
log.debug("Request IndexPage;")
|
||||
return response("Index page")
|
||||
|
||||
|
||||
@web_app.get("/method/{method}")
|
||||
async def _method(method, secret_key: str = None):
|
||||
# log.debug(f"Request method; kwargs: {kwargs}")
|
||||
is_auth = secret_key == config.WebAPI["secret_key"]
|
||||
spl = method.split(".")
|
||||
if len(spl) != 2:
|
||||
raise StarletteHTTPException(405)
|
||||
api_class, api_method = spl
|
||||
match api_class:
|
||||
case "events":
|
||||
match api_method, is_auth:
|
||||
case "get", False:
|
||||
return response(data_pool)
|
||||
raise StarletteHTTPException(404)
|
||||
|
||||
|
||||
async def _stop():
|
||||
await asyncio.sleep(1)
|
||||
if uvserver is not None:
|
||||
uvserver.should_exit = True
|
||||
data_run[0] = False
|
||||
|
||||
|
||||
@web_app.get("/stop")
|
||||
async def stop(secret_key: str):
|
||||
log.debug(f"Request stop; secret key: {secret_key}")
|
||||
if secret_key == config.WebAPI["secret_key"]:
|
||||
log.info("Stopping Web server")
|
||||
asyncio.create_task(_stop())
|
||||
return response("Web server stopped")
|
||||
|
||||
|
||||
@web_app.exception_handler(HTTPException)
|
||||
async def default_exception_handler(request: Request, exc: HTTPException):
|
||||
return response(
|
||||
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
error_code=exc.status_code, error_message=f"Internal Server Error: {exc.status_code}"
|
||||
)
|
||||
|
||||
|
||||
@web_app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
code = exc.status_code
|
||||
if code == status.HTTP_405_METHOD_NOT_ALLOWED:
|
||||
return response(code=code, error_message="Method Not Allowed")
|
||||
if code == status.HTTP_404_NOT_FOUND:
|
||||
return response(code=code, error_message="Method not Found")
|
||||
return response(code=code, error_message="Unhandled error..")
|
||||
|
||||
|
||||
@web_app.exception_handler(RequestValidationError)
|
||||
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
code = status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
return response(code=code, error_message="Request Validation Error")
|
||||
|
||||
|
||||
utils.hack_fastapi()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
uvconfig = uvicorn.Config(web_app,
|
||||
host=config.WebAPI["server_ip"],
|
||||
port=config.WebAPI["server_port"],
|
||||
loop="asyncio")
|
||||
uvserver = uvicorn.Server(uvconfig)
|
||||
uvserver.run()
|
||||
except KeyboardInterrupt or CancelledError:
|
||||
pass
|
||||
@@ -1,126 +0,0 @@
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import click
|
||||
import uvicorn.server as uvs
|
||||
from uvicorn.config import LOGGING_CONFIG
|
||||
|
||||
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:
|
||||
cfg = self.config
|
||||
if cfg.fd is not None:
|
||||
sock = listeners[0]
|
||||
logger.info(i18n.web_start.format(sock.getsockname()))
|
||||
elif cfg.uds is not None:
|
||||
logger.info(i18n.web_start.format(cfg.uds))
|
||||
else:
|
||||
addr_format = "%s://%s:%d"
|
||||
host = "0.0.0.0" if cfg.host is None else cfg.host
|
||||
if ":" in host:
|
||||
addr_format = "%s://[%s]:%d"
|
||||
port = cfg.port
|
||||
if port == 0:
|
||||
port = listeners[0].getsockname()[1]
|
||||
protocol_name = "https" if cfg.ssl else "http"
|
||||
message = i18n.web_start.format(addr_format)
|
||||
color_message = (i18n.web_start.format(click.style(addr_format, bold=True)))
|
||||
logger.info(message, protocol_name, host, port, extra={"color_message": color_message})
|
||||
|
||||
|
||||
async def ev_shutdown(self, sockets=None) -> None:
|
||||
logger.debug("Shutting down")
|
||||
for server in self.servers:
|
||||
server.close()
|
||||
for sock in sockets or []:
|
||||
sock.close()
|
||||
for server in self.servers:
|
||||
await server.wait_closed()
|
||||
for connection in list(self.server_state.connections):
|
||||
connection.shutdown()
|
||||
await asyncio.sleep(0.1)
|
||||
try:
|
||||
await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded", len(self.server_state.tasks))
|
||||
for t in self.server_state.tasks:
|
||||
if sys.version_info < (3, 9):
|
||||
t.cancel()
|
||||
else:
|
||||
t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
|
||||
if not self.force_exit:
|
||||
await self.lifespan.shutdown()
|
||||
|
||||
|
||||
async def on_startup(self) -> None:
|
||||
self.logger.debug("Waiting for application startup.")
|
||||
loop = asyncio.get_event_loop()
|
||||
main_lifespan_task = loop.create_task(self.main()) # noqa: F841
|
||||
startup_event = {"type": "lifespan.startup"}
|
||||
await self.receive_queue.put(startup_event)
|
||||
await self.startup_event.wait()
|
||||
if self.startup_failed or (self.error_occured and self.config.lifespan == "on"):
|
||||
self.logger.error("Application startup failed. Exiting.")
|
||||
self.should_exit = True
|
||||
else:
|
||||
self.logger.debug("Application startup complete.")
|
||||
|
||||
|
||||
async def on_shutdown(self) -> None:
|
||||
if self.error_occured:
|
||||
return
|
||||
self.logger.debug("Waiting for application shutdown.")
|
||||
shutdown_event = {"type": "lifespan.shutdown"}
|
||||
await self.receive_queue.put(shutdown_event)
|
||||
await self.shutdown_event.wait()
|
||||
if self.shutdown_failed or (self.error_occured and self.config.lifespan == "on"):
|
||||
self.logger.error("Application shutdown failed. Exiting.")
|
||||
self.should_exit = True
|
||||
else:
|
||||
self.logger.debug("Application shutdown complete.")
|
||||
|
||||
|
||||
def hack_fastapi():
|
||||
uvs.Server.shutdown = ev_shutdown
|
||||
uvs.Server._log_started_message = ev_log_started_message
|
||||
on.LifespanOn.startup = on_startup
|
||||
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")
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Developed by KuiToi Dev
|
||||
# File modules.__init__.py
|
||||
# Written by: SantaSpeen
|
||||
# Version 1.1
|
||||
# Licence: FPA
|
||||
# (c) kuitoi.su 2023
|
||||
from .ConsoleSystem import Console
|
||||
from .ConfigProvider import ConfigProvider, Config
|
||||
from .i18n import MultiLanguage
|
||||
from .ConsoleSystem import Console
|
||||
from .EventsSystem import EventsSystem
|
||||
from .PluginsLoader import PluginsLoader
|
||||
from .WebAPISystem import web_app
|
||||
from .WebAPISystem import _stop as stop_web
|
||||
from .RateLimiter import RateLimiter
|
||||
from .i18n import MultiLanguage
|
||||
|
||||
Reference in New Issue
Block a user