Spaces:
Sleeping
Sleeping
Sun Jun 8 12:53:28 CST 2025
Browse files- 0-git-all.sh +7 -0
- 1-git-commit-dev.sh +3 -0
- git-push-dev.sh → 2-git-push-dev.sh +0 -0
- git-merge.sh → 3-git-commit-merge copy.sh +3 -0
- app.py +4 -60
0-git-all.sh
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git checkout dev
|
| 2 |
+
git add .
|
| 3 |
+
git commit -m "$(date)"
|
| 4 |
+
git push
|
| 5 |
+
git checkout main
|
| 6 |
+
git merge dev
|
| 7 |
+
git checkout dev
|
1-git-commit-dev.sh
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git checkout dev
|
| 2 |
+
git add .
|
| 3 |
+
git commit -m "$(date)"
|
git-push-dev.sh → 2-git-push-dev.sh
RENAMED
|
File without changes
|
git-merge.sh → 3-git-commit-merge copy.sh
RENAMED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
git checkout main
|
| 2 |
git merge dev
|
| 3 |
git checkout dev
|
|
|
|
| 1 |
+
git checkout dev
|
| 2 |
+
git add .
|
| 3 |
+
git commit -m "$(date)"
|
| 4 |
git checkout main
|
| 5 |
git merge dev
|
| 6 |
git checkout dev
|
app.py
CHANGED
|
@@ -4,8 +4,7 @@ import httpx
|
|
| 4 |
import logging
|
| 5 |
import os, json
|
| 6 |
import re # 用于URL路径处理
|
| 7 |
-
from
|
| 8 |
-
from key_selector import KeySelector
|
| 9 |
|
| 10 |
def get_target_url(url: str) -> str:
|
| 11 |
"""将url参数变了转换为合法的目标url;from http/ or https/ to http:// or https://"""
|
|
@@ -20,9 +19,7 @@ logging.basicConfig(level=logging.INFO)
|
|
| 20 |
logger = logging.getLogger("uvicorn.error")
|
| 21 |
|
| 22 |
# 从环境变量获取配置
|
| 23 |
-
#
|
| 24 |
-
# AUTH_TOKEN = os.getenv("AUTH_TOKEN", "YIG8ANC8q2QxFV_Gf8qwkPdBj2EpsqGqlfc3qvSdg7ksVkZcokOUtQn43XGK0NK3UUdBlIkYzNWefco_Wu4RcKnB0kpNgtZ2nTeqNum0i3fTUEhEcWSlJtT8FQgRK7bi") # 安全认证令牌
|
| 25 |
-
X_Goog_Api_Key = os.getenv("X_Goog_Api_Key", "")
|
| 26 |
|
| 27 |
@app.get("/")
|
| 28 |
async def read_root():
|
|
@@ -32,40 +29,24 @@ async def read_root():
|
|
| 32 |
async def proxy(request: Request, path: str):
|
| 33 |
# 添加流式请求判断逻辑
|
| 34 |
is_streaming = ":streamGenerateContent" in path.lower()
|
| 35 |
-
print(f"path: {path}")
|
| 36 |
-
# path = "https/generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
| 37 |
-
# target_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
| 38 |
target_url = get_target_url(path)
|
| 39 |
-
# target_url = f"{target_url}?key={X_Goog_Api_Key}"
|
| 40 |
-
print(f"target_url: {target_url}")
|
| 41 |
method = request.method
|
| 42 |
headers = {k: v for k, v in request.headers.items()
|
| 43 |
# if k.lower() not in ["host", "connection", "Postman-Token", "content-length"]}
|
| 44 |
if k.lower() not in ["host", "content-length"]}
|
| 45 |
|
| 46 |
key_selector = KeySelector()
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
headers["X-Goog-Api-Key"] = key_selector.get_api_key()['key_value']
|
| 50 |
-
# print(key_selector.api_key_info.key_value)
|
| 51 |
-
# headers["host"] = "generativelanguage.googleapis.com"
|
| 52 |
|
| 53 |
try:
|
| 54 |
# 关键修复:禁用KeepAlive防止连接冲突
|
| 55 |
transport = httpx.AsyncHTTPTransport(retries=3, http1=True)
|
| 56 |
-
|
| 57 |
async with httpx.AsyncClient(
|
| 58 |
transport=transport,
|
| 59 |
timeout=httpx.Timeout(300.0, connect=30.0)
|
| 60 |
) as client:
|
| 61 |
# 处理请求体
|
| 62 |
req_content = await request.body()
|
| 63 |
-
print(f"Request headers: {headers}")
|
| 64 |
-
# print(f"req_content: {req_content}")
|
| 65 |
-
# req_content_str = req_content.decode('utf-8') # 假设内容是 UTF-8 编码
|
| 66 |
-
# print(f"req_content_str: {req_content_str}")
|
| 67 |
-
print(f'target_url: {target_url}')
|
| 68 |
-
print(f'method: {method}')
|
| 69 |
# 发送请求到上游服务
|
| 70 |
response = await client.request(
|
| 71 |
method=method,
|
|
@@ -74,7 +55,6 @@ async def proxy(request: Request, path: str):
|
|
| 74 |
content=req_content,
|
| 75 |
follow_redirects=True # 自动处理重定向
|
| 76 |
)
|
| 77 |
-
|
| 78 |
if is_streaming:
|
| 79 |
# 流式响应处理
|
| 80 |
async def stream_generator():
|
|
@@ -88,7 +68,6 @@ async def proxy(request: Request, path: str):
|
|
| 88 |
# 移除冲突头部
|
| 89 |
headers = dict(response.headers)
|
| 90 |
headers.pop("Content-Length", None)
|
| 91 |
-
|
| 92 |
return StreamingResponse(
|
| 93 |
content=stream_generator(),
|
| 94 |
status_code=response.status_code,
|
|
@@ -96,16 +75,12 @@ async def proxy(request: Request, path: str):
|
|
| 96 |
media_type="application/x-ndjson" # Gemini流式格式
|
| 97 |
)
|
| 98 |
else:
|
| 99 |
-
|
| 100 |
-
print(f"response.status_code: {response.status_code}")
|
| 101 |
-
print(f"response.text: {response.text}")
|
| 102 |
# 解析 JSON 字符串
|
| 103 |
try:
|
| 104 |
data = json.loads(response.text)
|
| 105 |
# 格式化输出 JSON 数据
|
| 106 |
formatted_json = json.dumps(data, ensure_ascii=False, indent=4)
|
| 107 |
-
print('formatted_json')
|
| 108 |
-
print(formatted_json)
|
| 109 |
# return formatted_json
|
| 110 |
return Response(
|
| 111 |
content=formatted_json,
|
|
@@ -113,23 +88,6 @@ async def proxy(request: Request, path: str):
|
|
| 113 |
)
|
| 114 |
except json.JSONDecodeError as e:
|
| 115 |
print(f"Error decoding JSON: {e}")
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
# # 详细记录错误响应
|
| 119 |
-
# if response.status_code >= 400:
|
| 120 |
-
# error_detail = response.text[:1000] # 增加错误详情长度
|
| 121 |
-
# logger.error(f"Upstream error {response.status_code}: {error_detail}")
|
| 122 |
-
# # 可选:记录完整响应到文件
|
| 123 |
-
# # with open("error.log", "a") as f:
|
| 124 |
-
# # f.write(f"\n--- {target_url} ---\n{response.text}\n")
|
| 125 |
-
|
| 126 |
-
# # 流式传输响应
|
| 127 |
-
# return StreamingResponse(
|
| 128 |
-
# content=response.aiter_bytes(),
|
| 129 |
-
# status_code=response.status_code,
|
| 130 |
-
# headers=dict(response.headers)
|
| 131 |
-
# )
|
| 132 |
-
|
| 133 |
except httpx.ConnectError as e:
|
| 134 |
logger.error(f"Connection failed to {target_url}: {e}")
|
| 135 |
raise HTTPException(502, f"无法连接到上游服务: {target_url}") # Modified error message
|
|
@@ -137,15 +95,11 @@ async def proxy(request: Request, path: str):
|
|
| 137 |
logger.error(f"Timeout: {e}")
|
| 138 |
raise HTTPException(504, "上游服务响应超时")
|
| 139 |
except httpx.HTTPError as e: # 捕获所有HTTP异常
|
| 140 |
-
print('111111111111111111111')
|
| 141 |
-
|
| 142 |
try:
|
| 143 |
# 安全地获取异常信息
|
| 144 |
error_type = type(e).__name__
|
| 145 |
-
|
| 146 |
# 尝试获取状态码(如果存在)
|
| 147 |
status_code = getattr(e, 'response', None) and e.response.status_code
|
| 148 |
-
|
| 149 |
# 安全地获取错误详情
|
| 150 |
error_detail = ""
|
| 151 |
try:
|
|
@@ -154,7 +108,6 @@ async def proxy(request: Request, path: str):
|
|
| 154 |
error_detail = e.response.text[:500] # 只取前500个字符
|
| 155 |
except Exception as ex:
|
| 156 |
error_detail = f"无法获取错误详情: {type(ex).__name__}"
|
| 157 |
-
|
| 158 |
# 安全地记录日志
|
| 159 |
logger.error(
|
| 160 |
"HTTP代理错误 | "
|
|
@@ -163,13 +116,10 @@ async def proxy(request: Request, path: str):
|
|
| 163 |
f"目标URL: {target_url} | "
|
| 164 |
f"详情: {error_detail[:200]}" # 日志中只记录前200字符
|
| 165 |
)
|
| 166 |
-
|
| 167 |
# 打印到控制台以便调试
|
| 168 |
-
print(f"111111111111111111111 - HTTP代理错误: {error_type}")
|
| 169 |
print(f"目标URL: {target_url}")
|
| 170 |
print(f"状态码: {status_code or 'N/A'}")
|
| 171 |
print(f"错误详情: {error_detail[:500]}")
|
| 172 |
-
|
| 173 |
except Exception as ex:
|
| 174 |
# 如果记录日志本身出错,使用最安全的方式记录
|
| 175 |
logger.error(f"记录HTTP错误时发生异常: {type(ex).__name__}")
|
|
@@ -180,12 +130,6 @@ async def proxy(request: Request, path: str):
|
|
| 180 |
status_code=502,
|
| 181 |
detail=f"网关服务错误: {error_type} (上游状态: {status_code or '未知'})"
|
| 182 |
)
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
print('111111111111111111111')
|
| 186 |
-
# logger.error(f"HTTP error: {str(e)}")
|
| 187 |
-
|
| 188 |
-
raise HTTPException(502, f"网关错误: {str(e)}")
|
| 189 |
except Exception as e:
|
| 190 |
logger.exception("Unexpected proxy error")
|
| 191 |
raise HTTPException(500, f"内部服务器错误: {str(e)}")
|
|
|
|
| 4 |
import logging
|
| 5 |
import os, json
|
| 6 |
import re # 用于URL路径处理
|
| 7 |
+
from key_selector import KeySelector # 自动选择key
|
|
|
|
| 8 |
|
| 9 |
def get_target_url(url: str) -> str:
|
| 10 |
"""将url参数变了转换为合法的目标url;from http/ or https/ to http:// or https://"""
|
|
|
|
| 19 |
logger = logging.getLogger("uvicorn.error")
|
| 20 |
|
| 21 |
# 从环境变量获取配置
|
| 22 |
+
# X_Goog_Api_Key = os.getenv("X_Goog_Api_Key", "")
|
|
|
|
|
|
|
| 23 |
|
| 24 |
@app.get("/")
|
| 25 |
async def read_root():
|
|
|
|
| 29 |
async def proxy(request: Request, path: str):
|
| 30 |
# 添加流式请求判断逻辑
|
| 31 |
is_streaming = ":streamGenerateContent" in path.lower()
|
|
|
|
|
|
|
|
|
|
| 32 |
target_url = get_target_url(path)
|
|
|
|
|
|
|
| 33 |
method = request.method
|
| 34 |
headers = {k: v for k, v in request.headers.items()
|
| 35 |
# if k.lower() not in ["host", "connection", "Postman-Token", "content-length"]}
|
| 36 |
if k.lower() not in ["host", "content-length"]}
|
| 37 |
|
| 38 |
key_selector = KeySelector()
|
| 39 |
+
headers["X-Goog-Api-Key"] = key_selector.get_api_key()['key_value'] # 从数据库获取API密钥
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
try:
|
| 42 |
# 关键修复:禁用KeepAlive防止连接冲突
|
| 43 |
transport = httpx.AsyncHTTPTransport(retries=3, http1=True)
|
|
|
|
| 44 |
async with httpx.AsyncClient(
|
| 45 |
transport=transport,
|
| 46 |
timeout=httpx.Timeout(300.0, connect=30.0)
|
| 47 |
) as client:
|
| 48 |
# 处理请求体
|
| 49 |
req_content = await request.body()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# 发送请求到上游服务
|
| 51 |
response = await client.request(
|
| 52 |
method=method,
|
|
|
|
| 55 |
content=req_content,
|
| 56 |
follow_redirects=True # 自动处理重定向
|
| 57 |
)
|
|
|
|
| 58 |
if is_streaming:
|
| 59 |
# 流式响应处理
|
| 60 |
async def stream_generator():
|
|
|
|
| 68 |
# 移除冲突头部
|
| 69 |
headers = dict(response.headers)
|
| 70 |
headers.pop("Content-Length", None)
|
|
|
|
| 71 |
return StreamingResponse(
|
| 72 |
content=stream_generator(),
|
| 73 |
status_code=response.status_code,
|
|
|
|
| 75 |
media_type="application/x-ndjson" # Gemini流式格式
|
| 76 |
)
|
| 77 |
else:
|
| 78 |
+
# 非流式响应处理
|
|
|
|
|
|
|
| 79 |
# 解析 JSON 字符串
|
| 80 |
try:
|
| 81 |
data = json.loads(response.text)
|
| 82 |
# 格式化输出 JSON 数据
|
| 83 |
formatted_json = json.dumps(data, ensure_ascii=False, indent=4)
|
|
|
|
|
|
|
| 84 |
# return formatted_json
|
| 85 |
return Response(
|
| 86 |
content=formatted_json,
|
|
|
|
| 88 |
)
|
| 89 |
except json.JSONDecodeError as e:
|
| 90 |
print(f"Error decoding JSON: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
except httpx.ConnectError as e:
|
| 92 |
logger.error(f"Connection failed to {target_url}: {e}")
|
| 93 |
raise HTTPException(502, f"无法连接到上游服务: {target_url}") # Modified error message
|
|
|
|
| 95 |
logger.error(f"Timeout: {e}")
|
| 96 |
raise HTTPException(504, "上游服务响应超时")
|
| 97 |
except httpx.HTTPError as e: # 捕获所有HTTP异常
|
|
|
|
|
|
|
| 98 |
try:
|
| 99 |
# 安全地获取异常信息
|
| 100 |
error_type = type(e).__name__
|
|
|
|
| 101 |
# 尝试获取状态码(如果存在)
|
| 102 |
status_code = getattr(e, 'response', None) and e.response.status_code
|
|
|
|
| 103 |
# 安全地获取错误详情
|
| 104 |
error_detail = ""
|
| 105 |
try:
|
|
|
|
| 108 |
error_detail = e.response.text[:500] # 只取前500个字符
|
| 109 |
except Exception as ex:
|
| 110 |
error_detail = f"无法获取错误详情: {type(ex).__name__}"
|
|
|
|
| 111 |
# 安全地记录日志
|
| 112 |
logger.error(
|
| 113 |
"HTTP代理错误 | "
|
|
|
|
| 116 |
f"目标URL: {target_url} | "
|
| 117 |
f"详情: {error_detail[:200]}" # 日志中只记录前200字符
|
| 118 |
)
|
|
|
|
| 119 |
# 打印到控制台以便调试
|
|
|
|
| 120 |
print(f"目标URL: {target_url}")
|
| 121 |
print(f"状态码: {status_code or 'N/A'}")
|
| 122 |
print(f"错误详情: {error_detail[:500]}")
|
|
|
|
| 123 |
except Exception as ex:
|
| 124 |
# 如果记录日志本身出错,使用最安全的方式记录
|
| 125 |
logger.error(f"记录HTTP错误时发生异常: {type(ex).__name__}")
|
|
|
|
| 130 |
status_code=502,
|
| 131 |
detail=f"网关服务错误: {error_type} (上游状态: {status_code or '未知'})"
|
| 132 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
except Exception as e:
|
| 134 |
logger.exception("Unexpected proxy error")
|
| 135 |
raise HTTPException(500, f"内部服务器错误: {str(e)}")
|