main.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. # ------------------- 日志配置 -------------------
  22. LOG_DIR = os.path.join("common", "logs", "alien_gateway")
  23. os.makedirs(LOG_DIR, exist_ok=True)
  24. def _init_logger():
  25. logger = logging.getLogger("alien_gateway")
  26. if logger.handlers:
  27. return logger
  28. logger.setLevel(logging.INFO)
  29. fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
  30. # 文件日志
  31. info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8")
  32. info_handler.setLevel(logging.INFO)
  33. info_handler.setFormatter(fmt)
  34. error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8")
  35. error_handler.setLevel(logging.ERROR)
  36. error_handler.setFormatter(fmt)
  37. # 控制台日志
  38. console_handler = logging.StreamHandler()
  39. console_handler.setFormatter(fmt)
  40. logger.addHandler(info_handler)
  41. logger.addHandler(error_handler)
  42. logger.addHandler(console_handler)
  43. return logger
  44. logger = _init_logger()
  45. @app.get("/health")
  46. async def health():
  47. return {"service": "alien_gateway", "status": "ok"}
  48. @app.get("/health/redis")
  49. async def redis_health():
  50. try:
  51. return check_redis_connection()
  52. except Exception as exc:
  53. logger.error("redis health check failed err=%s", exc)
  54. raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Redis unavailable")
  55. # 此模块未来将承担 JWT 签发、权限校验中间件、路由聚合等核心功能
  56. @app.post("/auth/login")
  57. async def login():
  58. return {"message": "Auth logic here"}
  59. HOP_BY_HOP_HEADERS: List[str] = [
  60. "connection",
  61. "keep-alive",
  62. "proxy-authenticate",
  63. "proxy-authorization",
  64. "te",
  65. "trailers",
  66. "transfer-encoding",
  67. "upgrade",
  68. ]
  69. def _clean_headers(headers):
  70. """移除 hop-by-hop 头,避免转发问题。"""
  71. return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP_HEADERS}
  72. async def _proxy(request: Request, target_url: str, service_tag: str) -> Response:
  73. """通用反向代理:转发请求体、头部、查询参数并回写下游响应。"""
  74. client_ip = request.client.host if request.client else "-"
  75. body = await request.body()
  76. headers = _clean_headers(dict(request.headers))
  77. try:
  78. async with httpx.AsyncClient(timeout=30.0) as client:
  79. resp = await client.request(
  80. request.method,
  81. target_url,
  82. content=body,
  83. headers=headers,
  84. params=request.query_params,
  85. )
  86. except Exception as exc:
  87. logger.error(
  88. "proxy to %s failed ip=%s url=%s err=%s",
  89. service_tag, client_ip, target_url, exc,
  90. )
  91. raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Upstream unavailable")
  92. return Response(
  93. content=resp.content,
  94. status_code=resp.status_code,
  95. headers=_clean_headers(resp.headers),
  96. media_type=resp.headers.get("content-type"),
  97. )
  98. @app.api_route("/api/store/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
  99. async def proxy_to_store(full_path: str, request: Request):
  100. """监听 43333 端口,转发 /api/store/* 到 alien_store 服务。"""
  101. target_url = f"{settings.STORE_BASE_URL}/api/store/{full_path}"
  102. return await _proxy(request, target_url, "store")
  103. @app.api_route("/api/contract/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
  104. async def proxy_to_contract(full_path: str, request: Request):
  105. """转发 /api/contract/* 到 alien_contract 服务。"""
  106. target_url = f"{settings.CONTRACT_BASE_URL}/api/contract/{full_path}"
  107. return await _proxy(request, target_url, "contract")
  108. if __name__ == "__main__":
  109. import uvicorn
  110. uvicorn.run(app, host="0.0.0.0", port=43333)