Gem2a / app /services /image_proxy_service.py
misonL's picture
Fallback to transparent GIF for failed image proxy
224e82b
Raw
History Blame Contribute Delete
5.82 kB
# app/services/image_proxy_service.py
import logging
import base64
import httpx
import asyncio # 导入 asyncio 模块
from fastapi import Request, HTTPException
from fastapi.responses import StreamingResponse, JSONResponse, Response # 导入 Response 类
from typing import Optional, Tuple
# 导入自定义模块
from app.core.gemini_client_manager import get_next_gemini_cookie_tuple # 从 core 模块导入获取 cookie 的函数
logger = logging.getLogger(__name__)
# 定义需要跳过后端下载的域名,这些域名的图片将通过代理加载
SKIP_DOWNLOAD_DOMAINS = ["googleusercontent.com", "blogger.googleusercontent.com", "lh3.googleusercontent.com"]
async def proxy_image_request(image_url: str, request: Request):
"""
代理图片下载,解决前端跨域或防盗链问题。
"""
logger.info(f"接收到图片代理请求: {image_url}")
# 获取下一组Gemini Cookie
cookie_tuple: Optional[Tuple[str, str]] = get_next_gemini_cookie_tuple()
cookies = {}
if cookie_tuple:
sid, sidts = cookie_tuple
cookies = {
"__Secure-1PSID": sid,
"__Secure-1PSIDTS": sidts
}
logger.info(f"使用Cookie代理图片下载: sid={sid[:5]}..., sidts={sidts[:5]}...")
# 设置更安全的请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://gemini.google.com/",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}
max_retries = 3
retry_delay = 1.0 # 重试间隔(秒)
for attempt in range(max_retries):
try:
async with httpx.AsyncClient(
follow_redirects=True,
trust_env=False,
max_redirects=5,
cookies=cookies,
http2=True
) as client:
# 添加详细日志
logger.debug(f"开始代理下载图片 (尝试 {attempt+1}/{max_retries}): {image_url}")
response = await client.get(image_url, timeout=30, headers=headers)
response.raise_for_status()
# 记录响应详情
logger.info(f"图片代理成功: URL={image_url}, 状态码={response.status_code}, 类型={response.headers.get('Content-Type')}")
# 返回流式响应
return StreamingResponse(
content=response.iter_bytes(),
media_type=response.headers.get("Content-Type", "image/jpeg"),
headers={
"Cache-Control": "public, max-age=86400", # 缓存一天
"Access-Control-Allow-Origin": "*"
}
)
except httpx.HTTPStatusError as e:
if attempt == max_retries - 1:
logger.error(f"代理下载图片失败: URL: {image_url}, 状态码: {e.response.status_code}")
# 返回一个透明的 1x1 像素 GIF 图片作为占位符
transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
transparent_gif_bytes = base64.b64decode(transparent_gif_base64)
return Response(
content=transparent_gif_bytes,
media_type="image/gif",
headers={
"Cache-Control": "public, max-age=60", # 短时间缓存
"Access-Control-Allow-Origin": "*" # 允许跨域
}
)
logger.warning(f"尝试 {attempt+1}/{max_retries} 失败,状态码: {e.response.status_code}")
except httpx.TooManyRedirects:
if attempt == max_retries - 1:
logger.error(f"代理下载图片重定向过多: URL: {image_url}")
# 返回一个透明的 1x1 像素 GIF 图片作为占位符
transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
transparent_gif_bytes = base64.b64decode(transparent_gif_base64)
return Response(
content=transparent_gif_bytes,
media_type="image/gif",
headers={
"Cache-Control": "public, max-age=60", # 短时间缓存
"Access-Control-Allow-Origin": "*" # 允许跨域
}
)
logger.warning(f"尝试 {attempt+1}/{max_retries} 失败: 重定向过多")
except (httpx.RequestError, Exception) as e:
if attempt == max_retries - 1:
logger.error(f"代理下载图片失败: URL: {image_url}, 错误: {type(e).__name__} - {e}")
# 返回一个透明的 1x1 像素 GIF 图片作为占位符
transparent_gif_base64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
transparent_gif_bytes = base64.b64decode(transparent_gif_base64)
return Response(
content=transparent_gif_bytes,
media_type="image/gif",
headers={
"Cache-Control": "public, max-age=60", # 短时间缓存
"Access-Control-Allow-Origin": "*" # 允许跨域
}
)
logger.warning(f"尝试 {attempt+1}/{max_retries} 失败: {type(e).__name__} - {e}")
# 等待一段时间后重试
await asyncio.sleep(retry_delay * (attempt + 1))