BotZ/api/app.py

109 lines
3.0 KiB
Python

# -*- encoding: utf-8 -*-
"""
The application entrypoint.
"""
from aiohttp import web
import base64
from cryptography import fernet
import logging
import logging.handlers
import os
import typing as T
import click
from aiohttp_session import setup, session_middleware
from aiohttp_session.cookie_storage import EncryptedCookieStorage
from bot_z.async_operator import AsyncOperator
from api.rest import routes, add_static_routes
from api.conf import read_conf
def setup_log(level: str, syslog: bool) -> logging.Logger:
alog = logging.getLogger("api")
alog.setLevel(os.environ.get("BOTZ_LOGLEVEL", level))
h = logging.StreamHandler()
f = logging.Formatter("%(levelname)s [%(name)s] -> %(message)s")
h.setFormatter(f)
alog.addHandler(h)
if syslog:
sh = logging.handlers.SysLogHandler()
alog.addHandler(sh)
return alog
def init_secret() -> bytes:
fernet_key = fernet.Fernet.generate_key()
return base64.urlsafe_b64decode(fernet_key)
def setup_session(app: web.Application, secure: bool, max_age: int):
secret = init_secret()
setup(
app,
EncryptedCookieStorage(
secret_key=secret,
cookie_name="BOTZ_SESSION",
httponly=False,
secure=secure,
max_age=max_age,
),
)
def run(
address: T.Optional[T.Text], port: T.Optional[int], conf_path: T.Optional[T.Text]
) -> None:
"""Application entrypoint."""
conf = read_conf(conf_path)
# This closure is needed to intercept the to-be-prepared response
# and add the right CORS headers
async def on_prepare_cors(request, response):
response.headers["Access-Control-Allow-Origin"] = conf["http"].get("cors_allow")
response.headers["Access-Control-Allow-Credentials"] = "true"
alog = setup_log(conf["log"]["level"], conf["log"]["syslog"])
alog.debug("conf %s", conf)
app = web.Application(logger=alog)
app["base_uri"] = conf["base_uri"]
app["debug"] = conf["debug"]
app["headless"] = conf["headless"]
if conf["http"].get("cors_allow"):
app.on_response_prepare.append(on_prepare_cors)
setup_session(app, conf["http"]["cookie_secure"], conf["http"]["session_timeout"])
add_static_routes(alog)
app.add_routes(routes)
addr = []
if address is not None:
addr = [address]
elif conf["http"].get("bind_addr"):
addr.extend(conf["http"]["bind_addr"])
else:
addr = ["127.0.0.1"]
if port is None:
port = conf["http"]["port"]
alog.debug("Starting app with: addr -> %s, port -> %d", addr, port)
web.run_app(app, host=addr, port=port)
@click.command()
@click.option(
"-a", "--address", type=click.STRING, help="Address to bind the server to."
)
@click.option("-p", "--port", type=click.INT, help="Port to bind to.")
@click.option(
"-c",
"--conf",
type=click.Path(exists=False),
help="A path to a configuration file.",
)
def cli(
address: T.Optional[T.Text],
port: T.Optional[int] = None,
conf: T.Optional[T.Text] = None,
) -> None:
run(address, port, conf)