2022-09-30 10:40:22 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-08-31 22:55:50 +02:00
|
|
|
import asyncio
|
|
|
|
import functools
|
2022-02-08 16:29:29 +01:00
|
|
|
import logging
|
2022-08-31 22:55:50 +02:00
|
|
|
import signal
|
|
|
|
import sys
|
2022-09-26 18:19:33 +02:00
|
|
|
from dataclasses import dataclass, field
|
2022-04-06 19:44:51 +02:00
|
|
|
from pathlib import Path
|
2022-10-03 21:43:30 +02:00
|
|
|
from typing import Any, Dict, Optional
|
2022-08-01 06:10:56 +02:00
|
|
|
|
2022-08-31 22:55:50 +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
|
2022-08-31 22:55:50 +02:00
|
|
|
from chia.util.chia_logging import initialize_logging
|
|
|
|
from chia.util.config import load_config
|
|
|
|
from chia.util.default_root import DEFAULT_ROOT_PATH
|
2022-10-03 21:43:30 +02:00
|
|
|
from chia.util.network import WebServer
|
2022-04-06 19:44:51 +02:00
|
|
|
from chia.util.path import path_from_root
|
2022-08-31 22:55:50 +02:00
|
|
|
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__)
|
2022-01-23 19:34:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
@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
|
2022-08-31 22:55:50 +02:00
|
|
|
shutdown_event: asyncio.Event
|
2022-10-03 21:43:30 +02:00
|
|
|
webserver: Optional[WebServer] = None
|
2022-09-26 18:19:33 +02:00
|
|
|
upnp: UPnP = field(default_factory=UPnP)
|
2022-02-08 16:29:29 +01:00
|
|
|
|
|
|
|
async def start(self) -> None:
|
2022-10-03 21:43:30 +02:00
|
|
|
if self.webserver is not None:
|
|
|
|
raise RuntimeError("DataLayerServer already started")
|
2022-08-31 22:55:50 +02:00
|
|
|
|
|
|
|
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
|
2022-09-26 18:19:33 +02:00
|
|
|
self.upnp.setup()
|
2022-07-16 23:17:48 +02:00
|
|
|
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)
|
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
self.webserver = await WebServer.create(
|
|
|
|
hostname=self.host_ip, port=self.port, routes=[web.get("/{filename}", self.file_handler)]
|
|
|
|
)
|
2022-08-31 22:55:50 +02:00
|
|
|
self.log.info("Started Data Layer HTTP Server.")
|
2022-02-08 16:29:29 +01:00
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
def close(self) -> None:
|
2022-08-31 22:55:50 +02:00
|
|
|
self.shutdown_event.set()
|
2022-07-16 23:17:48 +02:00
|
|
|
self.upnp.release(self.port)
|
2022-08-30 09:59:35 +02:00
|
|
|
# UPnP.shutdown() is a blocking call, waiting for the UPnP thread to exit
|
2022-07-16 23:17:48 +02:00
|
|
|
self.upnp.shutdown()
|
2022-03-02 21:53:23 +01:00
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
if self.webserver is not None:
|
|
|
|
self.webserver.close()
|
2022-08-31 22:55:50 +02:00
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
self.log.info("Stop triggered for Data Layer HTTP Server.")
|
2022-08-31 22:55:50 +02:00
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
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
|
2022-08-31 22:55:50 +02:00
|
|
|
|
|
|
|
def _accept_signal(self, signal_number: int, stack_frame: Any = None) -> None:
|
|
|
|
self.log.info("Got SIGINT or SIGTERM signal - stopping")
|
|
|
|
|
2022-10-03 21:43:30 +02:00
|
|
|
self.close()
|
2022-08-31 22:55:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
async def async_start(root_path: Path) -> int:
|
|
|
|
|
|
|
|
shutdown_event = asyncio.Event()
|
|
|
|
|
2022-09-02 02:05:02 +02:00
|
|
|
dl_config = load_config(
|
|
|
|
root_path=root_path,
|
|
|
|
filename="config.yaml",
|
|
|
|
sub_config=SERVICE_NAME,
|
|
|
|
fill_missing_services=True,
|
|
|
|
)
|
2022-08-31 22:55:50 +02:00
|
|
|
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()
|
2022-10-03 21:43:30 +02:00
|
|
|
await shutdown_event.wait()
|
|
|
|
await data_layer_server.await_closed()
|
2022-08-31 22:55:50 +02:00
|
|
|
|
|
|
|
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())
|