import asyncio from datetime import datetime, timedelta import logging import os import signal from uuid import uuid4 from aiohttp import web from cachetools import LRUCache from relay.config import RelayConfig from relay.database import RelayDatabase from relay.misc import ( check_open_port, DotDict, set_app, ) from relay.views import routes class Application(web.Application): def __init__(self, cfgpath, middlewares=None): web.Application.__init__(self, middlewares=middlewares) self["starttime"] = None self["running"] = False self["is_docker"] = bool(os.environ.get("DOCKER_RUNNING")) self["config"] = RelayConfig(cfgpath) if not self["config"].load(): self["config"].save() self["database"] = RelayDatabase(self["config"]) self["database"].load() self["cache"] = DotDict( { key: Cache(maxsize=self["config"][key]) for key in self["config"].cachekeys } ) self["semaphore"] = asyncio.Semaphore(self["config"].push_limit) self.set_signal_handler() set_app(self) @property def cache(self): return self["cache"] @property def config(self): return self["config"] @property def database(self): return self["database"] @property def is_docker(self): return self["is_docker"] @property def semaphore(self): return self["semaphore"] @property def uptime(self): if not self["starttime"]: return timedelta(seconds=0) uptime = datetime.now() - self["starttime"] return timedelta(seconds=uptime.seconds) def set_signal_handler(self): signal.signal(signal.SIGHUP, self.stop) signal.signal(signal.SIGINT, self.stop) signal.signal(signal.SIGQUIT, self.stop) signal.signal(signal.SIGTERM, self.stop) def run(self): if not check_open_port(self.config.listen, self.config.port): return logging.error( f"A server is already running on port {self.config.port}" ) for route in routes: self.router.add_route(*route) logging.info( f"Starting webserver at {self.config.host} ({self.config.listen}:{self.config.port})" ) asyncio.run(self.handle_run()) # Save off config before exit self.config.save() self.database.save() def stop(self, *_): self["running"] = False async def handle_run(self): self["running"] = True runner = web.AppRunner( self, access_log_format='%{X-Forwarded-For}i "%r" %s %b "%{User-Agent}i"' ) await runner.setup() site = web.TCPSite( runner, host=self.config.listen, port=self.config.port, reuse_address=True ) await site.start() self["starttime"] = datetime.now() while self["running"]: await asyncio.sleep(0.25) await site.stop() self["starttime"] = None self["running"] = False class Cache(LRUCache): def set_maxsize(self, value): self.__maxsize = int(value) @web.middleware async def request_id_middleware(request, handler): request.id = uuid4() return await handler(request)