Spaces:
Sleeping
Sleeping
update
Browse files- proxyserver-fastapi copy.py +89 -0
- proxyserver-fastapi.py +50 -1
proxyserver-fastapi copy.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, HTTPException
|
| 2 |
+
from fastapi.responses import StreamingResponse, Response
|
| 3 |
+
import httpx
|
| 4 |
+
import uvicorn
|
| 5 |
+
import asyncio
|
| 6 |
+
|
| 7 |
+
app = FastAPI()
|
| 8 |
+
|
| 9 |
+
@app.api_route("/v1/{url_path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
| 10 |
+
async def proxy_request(url_path: str, request: Request):
|
| 11 |
+
print(f"接收到的 url_path: {url_path}")
|
| 12 |
+
|
| 13 |
+
# url_path 示例: https/open.bigmodel.cn/api/paas/v4/chat/completions
|
| 14 |
+
# 找到第一个 '/' 的位置,分隔协议和域名
|
| 15 |
+
first_slash_idx = url_path.find('/')
|
| 16 |
+
if first_slash_idx == -1:
|
| 17 |
+
raise HTTPException(status_code=400, detail="无效的URL路径格式。期望协议/域名/路径。")
|
| 18 |
+
|
| 19 |
+
protocol = url_path[:first_slash_idx] # 'https'
|
| 20 |
+
print(f"解析出的协议: {protocol}")
|
| 21 |
+
|
| 22 |
+
# 找到第二个 '/' 的位置,分隔域名和实际的路径
|
| 23 |
+
second_slash_idx = url_path.find('/', first_slash_idx + 1)
|
| 24 |
+
|
| 25 |
+
if second_slash_idx == -1:
|
| 26 |
+
domain = url_path[first_slash_idx + 1:]
|
| 27 |
+
remaining_path = ''
|
| 28 |
+
else:
|
| 29 |
+
domain = url_path[first_slash_idx + 1:second_slash_idx]
|
| 30 |
+
remaining_path = url_path[second_slash_idx:]
|
| 31 |
+
|
| 32 |
+
target_url = f"{protocol}://{domain}{remaining_path}"
|
| 33 |
+
print(f"\n\n\n代理请求到 {target_url}")
|
| 34 |
+
|
| 35 |
+
# 转发原始请求的头部,排除 'Host' 头部以避免冲突
|
| 36 |
+
# FastAPI 的 request.headers 是不可变的,需要转换为字典
|
| 37 |
+
headers = {key: value for key, value in request.headers.items() if key.lower() != 'host'}
|
| 38 |
+
|
| 39 |
+
# 获取请求体
|
| 40 |
+
request_body = await request.body()
|
| 41 |
+
|
| 42 |
+
# 获取查询参数
|
| 43 |
+
query_params = request.query_params
|
| 44 |
+
|
| 45 |
+
async with httpx.AsyncClient(verify=True, follow_redirects=False) as client:
|
| 46 |
+
try:
|
| 47 |
+
# 使用 httpx 库向目标 URL 发送请求
|
| 48 |
+
resp = await client.request(
|
| 49 |
+
method=request.method,
|
| 50 |
+
url=target_url,
|
| 51 |
+
headers=headers,
|
| 52 |
+
content=request_body, # 使用 content 传递请求体
|
| 53 |
+
params=query_params,
|
| 54 |
+
timeout=30.0, # 设置超时时间为30秒
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# 打印目标 API 返回的实际状态码和响应体,用于调试
|
| 58 |
+
print(f"目标API响应状态码: {resp.status_code}")
|
| 59 |
+
print(f"目标API响应体: {resp.text[:500]}...") # 打印前500个字符,避免过长
|
| 60 |
+
|
| 61 |
+
# 构建响应头部
|
| 62 |
+
excluded_headers = ['content-encoding'] # 保持与 Flask 版本一致
|
| 63 |
+
response_headers = {
|
| 64 |
+
name: value for name, value in resp.headers.items()
|
| 65 |
+
if name.lower() not in excluded_headers
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# 返回流式响应内容
|
| 69 |
+
# httpx 的 .aiter_bytes() 返回异步迭代器
|
| 70 |
+
async def generate_response():
|
| 71 |
+
async for chunk in resp.aiter_bytes(chunk_size=8192):
|
| 72 |
+
yield chunk
|
| 73 |
+
|
| 74 |
+
return StreamingResponse(generate_response(), status_code=resp.status_code, headers=response_headers)
|
| 75 |
+
|
| 76 |
+
except httpx.RequestError as e:
|
| 77 |
+
error_detail = f"代理请求到 {target_url} 失败: {type(e).__name__} - {e}"
|
| 78 |
+
print(f"代理请求失败: {error_detail}")
|
| 79 |
+
if e.request:
|
| 80 |
+
print(f"请求信息: {e.request.method} {e.request.url}")
|
| 81 |
+
if hasattr(e, 'response') and e.response:
|
| 82 |
+
print(f"响应信息: {e.response.status_code} {e.response.text[:200]}...")
|
| 83 |
+
raise HTTPException(status_code=500, detail=error_detail)
|
| 84 |
+
|
| 85 |
+
# if __name__ == '__main__':
|
| 86 |
+
# # 提示:请确保您已激活 conda 环境 'any-api' (conda activate any-api)
|
| 87 |
+
# # 提示:请确保已安装 FastAPI, Uvicorn 和 httpx 库 (pip install fastapi uvicorn httpx)
|
| 88 |
+
# print(f"代理服务器正在 0.0.0.0:7860 上启动")
|
| 89 |
+
# uvicorn.run(app, host="0.0.0.0", port=7860)
|
proxyserver-fastapi.py
CHANGED
|
@@ -2,12 +2,57 @@ from fastapi import FastAPI, Request, HTTPException
|
|
| 2 |
from fastapi.responses import StreamingResponse, Response
|
| 3 |
import httpx
|
| 4 |
import uvicorn
|
| 5 |
-
import asyncio
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
app = FastAPI()
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
@app.api_route("/v1/{url_path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
| 10 |
async def proxy_request(url_path: str, request: Request):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
print(f"接收到的 url_path: {url_path}")
|
| 12 |
|
| 13 |
# url_path 示例: https/open.bigmodel.cn/api/paas/v4/chat/completions
|
|
@@ -36,6 +81,10 @@ async def proxy_request(url_path: str, request: Request):
|
|
| 36 |
# FastAPI 的 request.headers 是不可变的,需要转换为字典
|
| 37 |
headers = {key: value for key, value in request.headers.items() if key.lower() != 'host'}
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
# 获取请求体
|
| 40 |
request_body = await request.body()
|
| 41 |
|
|
|
|
| 2 |
from fastapi.responses import StreamingResponse, Response
|
| 3 |
import httpx
|
| 4 |
import uvicorn
|
| 5 |
+
import asyncio, os
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
# 加载 .env 文件中的环境变量
|
| 9 |
+
load_dotenv()
|
| 10 |
|
| 11 |
app = FastAPI()
|
| 12 |
|
| 13 |
+
# Placeholder for Airs Platform Token (replace with actual validation logic)
|
| 14 |
+
AIRS_PLATFORM_TOKEN = os.getenv("AIRS_PLATFORM_TOKEN", "")
|
| 15 |
+
|
| 16 |
+
# Dummy function to simulate database lookup for API key
|
| 17 |
+
async def get_api_key_from_db(user_identifier: str) -> str:
|
| 18 |
+
"""
|
| 19 |
+
Simulates fetching an API key from a database based on a user identifier.
|
| 20 |
+
In a real application, this would involve actual database queries.
|
| 21 |
+
"""
|
| 22 |
+
# For demonstration, return a fixed API key.
|
| 23 |
+
# In a real scenario, you might map user_identifier to a specific API key.
|
| 24 |
+
print(f"模拟数据库查找用户: {user_identifier}")
|
| 25 |
+
if user_identifier == "valid_user_id_from_token": # This would come from decoding the actual token
|
| 26 |
+
return "AIzaSyDCbN6WDNIEtB8yJmNX40ScechQu6mCZoo"
|
| 27 |
+
else:
|
| 28 |
+
raise HTTPException(status_code=403, detail="禁止访问: 未找到该用户的有效API密钥。")
|
| 29 |
+
|
| 30 |
@app.api_route("/v1/{url_path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
| 31 |
async def proxy_request(url_path: str, request: Request):
|
| 32 |
+
# --- Airs Platform Token Authorization ---
|
| 33 |
+
auth_header = request.headers.get("Authorization")
|
| 34 |
+
if not auth_header:
|
| 35 |
+
raise HTTPException(status_code=401, detail="未授权: 缺少Authorization头部。")
|
| 36 |
+
|
| 37 |
+
# 期望格式: "Bearer your_airs_platform_secret_token"
|
| 38 |
+
try:
|
| 39 |
+
scheme, token = auth_header.split()
|
| 40 |
+
if scheme.lower() != "bearer" or token != AIRS_PLATFORM_TOKEN:
|
| 41 |
+
raise HTTPException(status_code=401, detail="未授权: Airs平台令牌无效。")
|
| 42 |
+
except ValueError:
|
| 43 |
+
raise HTTPException(status_code=401, detail="未授权: Authorization头部格式无效。")
|
| 44 |
+
|
| 45 |
+
print("Airs平台令牌授权成功。")
|
| 46 |
+
|
| 47 |
+
# --- 从数据库检索第三方API密钥 ---
|
| 48 |
+
# 在实际场景中,'user_identifier' 将从已验证的Airs令牌中提取
|
| 49 |
+
# 在此示例中,我们使用占位符。
|
| 50 |
+
try:
|
| 51 |
+
third_party_api_key = await get_api_key_from_db("valid_user_id_from_token")
|
| 52 |
+
print(f"成功从数据库获取第三方API Key: {third_party_api_key[:5]}...") # 打印部分密钥以确保安全
|
| 53 |
+
except HTTPException as e:
|
| 54 |
+
raise e # 重新抛出来自get_api_key_from_db的HTTPException
|
| 55 |
+
|
| 56 |
print(f"接收到的 url_path: {url_path}")
|
| 57 |
|
| 58 |
# url_path 示例: https/open.bigmodel.cn/api/paas/v4/chat/completions
|
|
|
|
| 81 |
# FastAPI 的 request.headers 是不可变的,需要转换为字典
|
| 82 |
headers = {key: value for key, value in request.headers.items() if key.lower() != 'host'}
|
| 83 |
|
| 84 |
+
# 将检索到的第三方API密钥添加到目标API的请求头中
|
| 85 |
+
headers["Authorization"] = f"Bearer {third_party_api_key}"
|
| 86 |
+
print(f"添加第三方API Key到请求头: Authorization: Bearer {third_party_api_key[:5]}...")
|
| 87 |
+
|
| 88 |
# 获取请求体
|
| 89 |
request_body = await request.body()
|
| 90 |
|