diff --git a/Dockerfile b/Dockerfile index af83d197..7f534ff1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN set -ex && \ su-exec \ shadow \ tini \ + curl \ openssl \ tzdata && \ python3 -m pip install --no-cache-dir --upgrade pip && \ @@ -39,6 +40,9 @@ RUN set -ex && \ COPY --chmod=755 backend/src/. . COPY --chmod=755 entrypoint.sh /entrypoint.sh +# Add healthcheck endpoint +HEALTHCHECK --interval=5s --timeout=10s --retries=3 CMD curl --silent --fail http://localhost:7892/api/v1/health || exit 1 + ENTRYPOINT ["tini", "-g", "--", "/entrypoint.sh"] EXPOSE 7892 diff --git a/backend/src/module/api/__init__.py b/backend/src/module/api/__init__.py index 38999e4d..d0caa245 100644 --- a/backend/src/module/api/__init__.py +++ b/backend/src/module/api/__init__.py @@ -7,6 +7,7 @@ from .program import router as program_router from .rss import router as rss_router from .search import router as search_router +from .health import router as health_router __all__ = "v1" @@ -19,3 +20,4 @@ v1.include_router(config_router) v1.include_router(rss_router) v1.include_router(search_router) +v1.include_router(health_router) diff --git a/backend/src/module/api/health.py b/backend/src/module/api/health.py new file mode 100644 index 00000000..bdb62300 --- /dev/null +++ b/backend/src/module/api/health.py @@ -0,0 +1,46 @@ +import logging +from fastapi import APIRouter +from fastapi.responses import JSONResponse +from typing import Literal + +from module.models import APIResponse +from module.checker import Checker + +router = APIRouter(prefix="/health", tags=["health"]) +logger = logging.getLogger(__name__) + +current_health_status = "healthy" + +@router.get("", response_model=APIResponse) +async def health_check(): + global current_health_status + if not Checker.check_downloader(): + current_health_status = "unhealthy" + if current_health_status == "healthy": + return JSONResponse( + status_code=200, + content={"status": "healthy"}, + ) + else: + current_health_status = "unhealthy" + return JSONResponse( + status_code=500, + content={"status": "unhealthy"}, + ) + +@router.patch("", response_model=APIResponse) +async def update_health_status(status: Literal["healthy", "unhealthy"]): + global current_health_status + try: + logger.debug(f"[Health] Health status changed from {current_health_status} to {status}") + current_health_status = status + return JSONResponse( + status_code=200, + content={"msg_en": "Health status updated successfully.", "msg_zh": "健康状态更新成功。"}, + ) + except Exception as e: + logger.warning(f"[Health] Health status updated failed: {str(e)}") + return JSONResponse( + status_code=406, + content={"msg_en": "Health status updated failed.", "msg_zh": "健康状态更新失败。"}, + ) \ No newline at end of file diff --git a/backend/src/module/network/request_url.py b/backend/src/module/network/request_url.py index 0b85e77e..12bfb6ce 100644 --- a/backend/src/module/network/request_url.py +++ b/backend/src/module/network/request_url.py @@ -14,6 +14,21 @@ class RequestURL: def __init__(self): self.header = {"user-agent": "Mozilla/5.0", "Accept": "application/xml"} self._socks5_proxy = False + + # Patch the health api endpoint if Network connection was changed + def change_health_status(self,health_status): + health_check_url = f"http://localhost:7892/api/v1/health?status={health_status}" + + try: + response = requests.patch(health_check_url) + response.raise_for_status() + logger.debug( + f"[Health] Health status changed to {health_status}." + ) + except requests.exceptions.RequestException as e: + logger.debug( + f"[Health] Failed to update health status: {e}" + ) def get_url(self, url, retry=3): try_time = 0 @@ -22,6 +37,7 @@ def get_url(self, url, retry=3): req = self.session.get(url=url, headers=self.header, timeout=5) logger.debug(f"[Network] Successfully connected to {url}. Status: {req.status_code}") req.raise_for_status() + self.change_health_status("healthy") return req except requests.RequestException: logger.debug( @@ -35,6 +51,7 @@ def get_url(self, url, retry=3): logger.debug(e) break logger.error(f"[Network] Unable to connect to {url}, Please check your network settings") + self.change_health_status("unhealthy") return None def post_url(self, url: str, data: dict, retry=3): @@ -45,6 +62,7 @@ def post_url(self, url: str, data: dict, retry=3): url=url, headers=self.header, data=data, timeout=5 ) req.raise_for_status() + self.change_health_status("healthy") return req except requests.RequestException: logger.warning( @@ -59,6 +77,7 @@ def post_url(self, url: str, data: dict, retry=3): break logger.error(f"[Network] Failed connecting to {url}") logger.warning("[Network] Please check DNS/Connection settings") + self.change_health_status("unhealthy") return None def check_url(self, url: str):