diff --git a/requirements.txt b/requirements.txt index 0195b38..c28a27e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ fastapi[standard] -uvicorn \ No newline at end of file +uvicorn +pydantic +pydantic-settings +ntplib diff --git a/src/gentrade_server/config.py b/src/gentrade_server/config.py new file mode 100644 index 0000000..88edf4b --- /dev/null +++ b/src/gentrade_server/config.py @@ -0,0 +1,9 @@ +""" +Configure +""" +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + ntp_server: str = "ntp.ntsc.ac.cn" + +settings = Settings() diff --git a/src/gentrade_server/main.py b/src/gentrade_server/main.py index ecfb774..e2ef207 100644 --- a/src/gentrade_server/main.py +++ b/src/gentrade_server/main.py @@ -1,13 +1,38 @@ """ The main entry """ +import logging import uvicorn +from contextlib import asynccontextmanager from fastapi import FastAPI, Depends +from fastapi.middleware.cors import CORSMiddleware from .routers import secure, public from .auth import get_user +from .util import sync_ntp_server + +logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') +LOG = logging.getLogger(__name__) + +@asynccontextmanager +async def lifespan(_:FastAPI): + """ + App lifecycle + """ + LOG.info("Starting Up...") + sync_ntp_server() + yield + LOG.info("Shutting Down...") app = FastAPI() +app = FastAPI(lifespan=lifespan) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) app.include_router( public.router, diff --git a/src/gentrade_server/routers/public.py b/src/gentrade_server/routers/public.py index 55911cf..9bdf5c3 100644 --- a/src/gentrade_server/routers/public.py +++ b/src/gentrade_server/routers/public.py @@ -1,7 +1,18 @@ """ Public result API interfaces """ +import logging +import ntplib +import datetime +from dateutil.tz import tzlocal + from fastapi import APIRouter +from pydantic import BaseModel + +from ..util import sync_ntp_server +from ..config import settings + +LOG = logging.getLogger(__name__) router = APIRouter() @@ -11,3 +22,38 @@ async def get_testroute(): Test public interface """ return "OK" + +class HealthCheck(BaseModel): + """ + Response model to validate and return when performing a health check. + """ + + status: str = "OK" + +@router.get("/health") +async def get_health() -> HealthCheck: + return HealthCheck(status="OK") + +@router.get("/settings") +async def get_settings(): + return { + 'ntp_server': settings.ntp_server + } + +import time + + + +@router.get("/server_time") +async def get_server_time(): + curr_ts = time.time() + now_utc = datetime.datetime.fromtimestamp(curr_ts, datetime.UTC) + tl = tzlocal() + ntp_offset = sync_ntp_server() + + return { + 'ntp_offset': ntp_offset, + 'timezone_name': tl.tzname(now_utc), + 'timezone_offset': tl.utcoffset(now_utc).total_seconds(), + 'timestamp_server': int(curr_ts) + } diff --git a/src/gentrade_server/util.py b/src/gentrade_server/util.py new file mode 100644 index 0000000..913fa67 --- /dev/null +++ b/src/gentrade_server/util.py @@ -0,0 +1,32 @@ +""" +Utils +""" +import logging +import ntplib + +LOG = logging.getLogger(__name__) + +def sync_ntp_server() -> float: + """ + Sync with NTP server + + :return : offset in seconds + """ + ntp_servers = ['ntp.ntsc.ac.cn', 'ntp.sjtu.edu.cn', 'cn.ntp.org.cn', + 'cn.pool.ntp.org', 'ntp.aliyun.com'] + retry = len(ntp_servers) - 1 + client = ntplib.NTPClient() + while retry > 0: + LOG.info("Try to get time from NTP: %s", ntp_servers[retry]) + + try: + ret = client.request(ntp_servers[retry], version=3) + offset = (ret.recv_time - ret.orig_time + + ret.dest_time - ret.tx_time) / 2 + LOG.info("NTP offset: %.2f", offset) + return offset + except ntplib.NTPException: + LOG.error("Fail to get time, try another") + retry -= 1 + continue + return None