xch-blockchain/chia/data_layer/data_layer_server.py

159 lines
4.9 KiB
Python
Raw Normal View History

from __future__ import annotations
import asyncio
import functools
2022-02-08 16:29:29 +01:00
import logging
import signal
import sys
from dataclasses import dataclass, field
2022-04-06 19:44:51 +02:00
from pathlib import Path
from typing import Any, Dict, Optional
2022-08-01 06:10:56 +02:00
import click
2022-04-06 16:48:34 +02:00
from aiohttp import web
2022-08-01 06:10:56 +02:00
2022-06-07 16:54:15 +02:00
from chia.data_layer.download_data import is_filename_valid
2022-08-01 06:10:56 +02:00
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
2022-04-06 19:44:51 +02:00
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:
2022-04-06 19:44:51 +02:00
root_path: Path
2022-02-08 16:29:29 +01:00
config: Dict[str, Any]
log: logging.Logger
shutdown_event: asyncio.Event
webserver: Optional[WebServer] = None
upnp: UPnP = field(default_factory=UPnP)
2022-02-08 16:29:29 +01:00
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"]
2022-03-02 21:53:23 +01:00
self.port = self.config["host_port"]
# Setup UPnP for the data_layer_service port
self.upnp.setup()
self.upnp.remap(self.port)
2022-03-02 21:53:23 +01:00
2022-04-07 16:55:02 +02:00
server_files_replaced: str = self.config.get(
"server_files_location", "data_layer/db/server_files_location_CHALLENGE"
).replace("CHALLENGE", self.config["selected_network"])
2022-04-06 19:44:51 +02:00
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.")
2022-02-08 16:29:29 +01:00
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()
2022-03-02 21:53:23 +01:00
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
2022-02-08 16:29:29 +01:00
2022-04-06 20:00:13 +02:00
async def file_handler(self, request: web.Request) -> web.Response:
2022-04-06 19:44:51 +02:00
filename = request.match_info["filename"]
2022-06-13 15:26:47 +02:00
if not is_filename_valid(filename):
2022-06-07 16:54:15 +02:00
raise Exception("Invalid file format requested.")
file_path = self.server_dir.joinpath(filename)
2022-06-06 19:32:12 +02:00
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())