xch-blockchain/chia/data_layer/data_layer_server.py
dustinface 8cdb431064
data_layer: Use WebServer in DataLayerServer (#13546)
* Don't wait on shutdown inside `start`

* `stop` -> `close`

* Use `WebServer`
2022-10-03 14:43:30 -05:00

158 lines
4.9 KiB
Python

from __future__ import annotations
import asyncio
import functools
import logging
import signal
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, Optional
import click
from aiohttp import web
from chia.data_layer.download_data import is_filename_valid
from chia.server.upnp import UPnP
from chia.util.chia_logging import initialize_logging
from chia.util.config import load_config
from chia.util.default_root import DEFAULT_ROOT_PATH
from chia.util.network import WebServer
from chia.util.path import path_from_root
from chia.util.setproctitle import setproctitle
# from chia.cmds.chia import monkey_patch_click
# See: https://bugs.python.org/issue29288
"".encode("idna")
SERVICE_NAME = "data_layer"
log = logging.getLogger(__name__)
@dataclass
class DataLayerServer:
root_path: Path
config: Dict[str, Any]
log: logging.Logger
shutdown_event: asyncio.Event
webserver: Optional[WebServer] = None
upnp: UPnP = field(default_factory=UPnP)
async def start(self) -> None:
if self.webserver is not None:
raise RuntimeError("DataLayerServer already started")
if sys.platform == "win32" or sys.platform == "cygwin":
# pylint: disable=E1101
signal.signal(signal.SIGBREAK, self._accept_signal)
signal.signal(signal.SIGINT, self._accept_signal)
signal.signal(signal.SIGTERM, self._accept_signal)
else:
loop = asyncio.get_running_loop()
loop.add_signal_handler(
signal.SIGINT,
functools.partial(self._accept_signal, signal_number=signal.SIGINT),
)
loop.add_signal_handler(
signal.SIGTERM,
functools.partial(self._accept_signal, signal_number=signal.SIGTERM),
)
self.log.info("Starting Data Layer HTTP Server.")
self.host_ip = self.config["host_ip"]
self.port = self.config["host_port"]
# Setup UPnP for the data_layer_service port
self.upnp.setup()
self.upnp.remap(self.port)
server_files_replaced: str = self.config.get(
"server_files_location", "data_layer/db/server_files_location_CHALLENGE"
).replace("CHALLENGE", self.config["selected_network"])
self.server_dir = path_from_root(self.root_path, server_files_replaced)
self.webserver = await WebServer.create(
hostname=self.host_ip, port=self.port, routes=[web.get("/{filename}", self.file_handler)]
)
self.log.info("Started Data Layer HTTP Server.")
def close(self) -> None:
self.shutdown_event.set()
self.upnp.release(self.port)
# UPnP.shutdown() is a blocking call, waiting for the UPnP thread to exit
self.upnp.shutdown()
if self.webserver is not None:
self.webserver.close()
self.log.info("Stop triggered for Data Layer HTTP Server.")
async def await_closed(self) -> None:
self.log.info("Wait for Data Layer HTTP Server shutdown.")
if self.webserver is not None:
await self.webserver.await_closed()
self.webserver = None
async def file_handler(self, request: web.Request) -> web.Response:
filename = request.match_info["filename"]
if not is_filename_valid(filename):
raise Exception("Invalid file format requested.")
file_path = self.server_dir.joinpath(filename)
with open(file_path, "rb") as reader:
content = reader.read()
response = web.Response(
content_type="application/octet-stream",
headers={"Content-Disposition": "attachment;filename={}".format(filename)},
body=content,
)
return response
def _accept_signal(self, signal_number: int, stack_frame: Any = None) -> None:
self.log.info("Got SIGINT or SIGTERM signal - stopping")
self.close()
async def async_start(root_path: Path) -> int:
shutdown_event = asyncio.Event()
dl_config = load_config(
root_path=root_path,
filename="config.yaml",
sub_config=SERVICE_NAME,
fill_missing_services=True,
)
setproctitle("data_layer_http")
initialize_logging(
service_name="data_layer_http",
logging_config=dl_config["logging"],
root_path=root_path,
)
data_layer_server = DataLayerServer(root_path, dl_config, log, shutdown_event)
await data_layer_server.start()
await shutdown_event.wait()
await data_layer_server.await_closed()
return 0
@click.command()
@click.option(
"-r",
"--root-path",
type=click.Path(exists=True, writable=True, file_okay=False),
default=DEFAULT_ROOT_PATH,
show_default=True,
help="Config file root",
)
def main(root_path: str = str(DEFAULT_ROOT_PATH)) -> int:
return asyncio.run(async_start(Path(root_path)))
if __name__ == "__main__":
sys.exit(main())