main.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import logging
  2. import os
  3. from typing import List
  4. import httpx
  5. from fastapi import FastAPI, Request, Response, HTTPException
  6. from fastapi.middleware.cors import CORSMiddleware
  7. from starlette.status import HTTP_502_BAD_GATEWAY
  8. from alien_gateway.config import settings
  9. from alien_util.redis_client import check_redis_connection
  10. app = FastAPI(
  11. title=f"{settings.PROJECT_NAME} - Gateway & Auth Service",
  12. version="1.0.0",
  13. )
  14. app.add_middleware(
  15. CORSMiddleware,
  16. allow_origins=["*"],
  17. allow_credentials=True,
  18. allow_methods=["*"],
  19. allow_headers=["*"],
  20. )
  21. LOG_DIR = os.path.join("common", "logs", "alien_gateway")
  22. os.makedirs(LOG_DIR, exist_ok=True)
  23. def _init_logger():
  24. logger = logging.getLogger("alien_gateway")
  25. if logger.handlers:
  26. return logger
  27. logger.setLevel(logging.INFO)
  28. fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
  29. info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8")
  30. info_handler.setLevel(logging.INFO)
  31. info_handler.setFormatter(fmt)
  32. error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8")
  33. error_handler.setLevel(logging.ERROR)
  34. error_handler.setFormatter(fmt)
  35. console_handler = logging.StreamHandler()
  36. console_handler.setFormatter(fmt)
  37. logger.addHandler(info_handler)
  38. logger.addHandler(error_handler)
  39. logger.addHandler(console_handler)
  40. return logger
  41. logger = _init_logger()
  42. @app.get("/health")
  43. async def health():
  44. return {"service": "alien_gateway", "status": "ok", "env": settings.APP_ENV}
  45. @app.get("/health/redis")
  46. async def redis_health():
  47. try:
  48. return check_redis_connection()
  49. except Exception as exc:
  50. logger.error("redis health check failed err=%s", exc)
  51. raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Redis unavailable")
  52. # 此模块未来将承担 JWT 签发、权限校验中间件、路由聚合等核心功能
  53. @app.post("/auth/login")
  54. async def login():
  55. return {"message": "Auth logic here"}
  56. HOP_BY_HOP_HEADERS: List[str] = [
  57. "connection",
  58. "keep-alive",
  59. "proxy-authenticate",
  60. "proxy-authorization",
  61. "te",
  62. "trailers",
  63. "transfer-encoding",
  64. "upgrade",
  65. ]
  66. def _clean_headers(headers):
  67. return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP_HEADERS}
  68. async def _proxy(request: Request, target_url: str, service_tag: str) -> Response:
  69. """通用反向代理:转发请求体、头部、查询参数并回写下游响应。"""
  70. client_ip = request.client.host if request.client else "-"
  71. body = await request.body()
  72. headers = _clean_headers(dict(request.headers))
  73. try:
  74. async with httpx.AsyncClient(timeout=30.0) as client:
  75. resp = await client.request(
  76. request.method,
  77. target_url,
  78. content=body,
  79. headers=headers,
  80. params=request.query_params,
  81. )
  82. except Exception as exc:
  83. logger.error(
  84. "proxy to %s failed ip=%s url=%s err=%s",
  85. service_tag, client_ip, target_url, exc,
  86. )
  87. raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Upstream unavailable")
  88. return Response(
  89. content=resp.content,
  90. status_code=resp.status_code,
  91. headers=_clean_headers(resp.headers),
  92. media_type=resp.headers.get("content-type"),
  93. )
  94. @app.api_route("/api/store/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
  95. async def proxy_to_store(full_path: str, request: Request):
  96. target_url = f"{settings.STORE_BASE_URL}/api/store/{full_path}"
  97. return await _proxy(request, target_url, "store")
  98. @app.api_route("/api/contract/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
  99. async def proxy_to_contract(full_path: str, request: Request):
  100. target_url = f"{settings.CONTRACT_BASE_URL}/api/contract/{full_path}"
  101. return await _proxy(request, target_url, "contract")
  102. if __name__ == "__main__":
  103. import uvicorn
  104. uvicorn.run(app, host="0.0.0.0", port=settings.GATEWAY_PORT)