| | from fastapi import FastAPI, Request, Response |
| | from fastapi.responses import StreamingResponse |
| | from fastapi.middleware.cors import CORSMiddleware |
| | import httpx |
| | import json |
| | from typing import Optional |
| |
|
| | app = FastAPI() |
| |
|
| | app.add_middleware( |
| | CORSMiddleware, |
| | allow_origins=["*"], |
| | allow_credentials=True, |
| | allow_methods=["*"], |
| | allow_headers=["*"], |
| | ) |
| |
|
| | TARGET_URL = "http://beibeioo.top" |
| |
|
| | async def get_http_client(): |
| | return httpx.AsyncClient( |
| | timeout=30.0, |
| | follow_redirects=True, |
| | http2=True |
| | ) |
| |
|
| | async def is_stream_request(request: Request) -> bool: |
| | |
| | accept = request.headers.get('accept', '') |
| | |
| | stream = request.query_params.get('stream', '').lower() |
| | return 'text/event-stream' in accept or stream == 'true' |
| |
|
| | async def proxy_handler(url: str, request: Request): |
| | try: |
| | headers = dict(request.headers) |
| | headers.pop('host', None) |
| | headers.pop('connection', None) |
| | headers.pop('content-length', None) |
| | headers.pop('transfer-encoding', None) |
| | |
| | params = dict(request.query_params) |
| | body = await request.body() |
| | |
| | async with await get_http_client() as client: |
| | if await is_stream_request(request): |
| | |
| | response = await client.stream( |
| | method=request.method, |
| | url=url, |
| | params=params, |
| | headers=headers, |
| | content=body if body else None, |
| | ) |
| | |
| | response_headers = dict(response.headers) |
| | response_headers.pop('transfer-encoding', None) |
| | response_headers.pop('content-encoding', None) |
| | response_headers.pop('content-length', None) |
| |
|
| | async def stream_generator(): |
| | try: |
| | buffer = b"" |
| | async for chunk in response.aiter_raw(): |
| | |
| | if chunk: |
| | yield chunk |
| | except Exception as e: |
| | print(f"Streaming error: {e}") |
| | finally: |
| | await response.aclose() |
| |
|
| | return StreamingResponse( |
| | stream_generator(), |
| | status_code=response.status_code, |
| | headers=response_headers, |
| | media_type=response.headers.get('content-type') |
| | ) |
| | else: |
| | |
| | response = await client.request( |
| | method=request.method, |
| | url=url, |
| | params=params, |
| | headers=headers, |
| | content=body if body else None, |
| | ) |
| | |
| | response_headers = dict(response.headers) |
| | response_headers.pop('transfer-encoding', None) |
| | response_headers.pop('content-encoding', None) |
| | response_headers.pop('content-length', None) |
| | |
| | return Response( |
| | content=response.content, |
| | status_code=response.status_code, |
| | headers=response_headers, |
| | media_type=response.headers.get('content-type') |
| | ) |
| | |
| | except httpx.TimeoutException: |
| | return Response(content="请求超时", status_code=504) |
| | except httpx.RequestError: |
| | return Response(content="无法连接到目标服务器", status_code=502) |
| | except Exception as e: |
| | return Response(content=f"服务器错误: {str(e)}", status_code=500) |
| |
|
| | |
| | @app.api_route("/api/v1/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"]) |
| | async def api_v1_proxy(path: str, request: Request): |
| | url = f"{TARGET_URL}/v1/{path}" |
| | return await proxy_handler(url, request) |
| |
|
| | |
| | @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"]) |
| | async def root_proxy(path: str, request: Request): |
| | if not path or path == "/": |
| | url = TARGET_URL |
| | else: |
| | url = f"{TARGET_URL}/{path}" |
| | return await proxy_handler(url, request) |
| |
|
| | @app.get("/healthcheck") |
| | async def healthcheck(): |
| | return {"status": "healthy"} |
| |
|
| | if __name__ == "__main__": |
| | import uvicorn |
| | uvicorn.run(app, host="0.0.0.0", port=7860) |