import logging import os from typing import List import httpx from fastapi import FastAPI, Request, Response, HTTPException from fastapi.middleware.cors import CORSMiddleware from starlette.status import HTTP_502_BAD_GATEWAY from alien_gateway.config import settings from alien_util.redis_client import check_redis_connection app = FastAPI( title=f"{settings.PROJECT_NAME} - Gateway & Auth Service", version="1.0.0" ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ------------------- 日志配置 ------------------- LOG_DIR = os.path.join("common", "logs", "alien_gateway") os.makedirs(LOG_DIR, exist_ok=True) def _init_logger(): logger = logging.getLogger("alien_gateway") if logger.handlers: return logger logger.setLevel(logging.INFO) fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s") # 文件日志 info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8") info_handler.setLevel(logging.INFO) info_handler.setFormatter(fmt) error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8") error_handler.setLevel(logging.ERROR) error_handler.setFormatter(fmt) # 控制台日志 console_handler = logging.StreamHandler() console_handler.setFormatter(fmt) logger.addHandler(info_handler) logger.addHandler(error_handler) logger.addHandler(console_handler) return logger logger = _init_logger() @app.get("/health") async def health(): return {"service": "alien_gateway", "status": "ok"} @app.get("/health/redis") async def redis_health(): try: return check_redis_connection() except Exception as exc: logger.error("redis health check failed err=%s", exc) raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Redis unavailable") # 此模块未来将承担 JWT 签发、权限校验中间件、路由聚合等核心功能 @app.post("/auth/login") async def login(): return {"message": "Auth logic here"} HOP_BY_HOP_HEADERS: List[str] = [ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", ] def _clean_headers(headers): """移除 hop-by-hop 头,避免转发问题。""" return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP_HEADERS} @app.api_route("/api/store/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) async def proxy_to_store(full_path: str, request: Request): """ 简易网关:监听 33333 端口,将 /api/store/* 转发到 alien_store 服务。 """ target_url = f"{settings.STORE_BASE_URL}/api/store/{full_path}" client_ip = request.client.host if request.client else "-" # 读取请求体 body = await request.body() # 过滤头部 headers = _clean_headers(dict(request.headers)) try: async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.request( request.method, target_url, content=body, headers=headers, params=request.query_params, ) except Exception as exc: logger.error("proxy to store failed ip=%s url=%s err=%s", client_ip, target_url, exc) raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Upstream unavailable") # 返回下游响应 return Response( content=resp.content, status_code=resp.status_code, headers=_clean_headers(resp.headers), media_type=resp.headers.get("content-type"), ) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=33333)