mirror of
https://github.com/TheSmallHanCat/flow2api.git
synced 2026-05-08 06:49:25 +08:00
feat: warm remote browser pool and tighten timeouts
This commit is contained in:
@@ -54,11 +54,11 @@ timeout = 7200 # 缓存超时时间(秒), 默认2小时
|
||||
base_url = "" # 缓存文件访问的基础URL, 留空则使用服务器地址
|
||||
|
||||
[captcha]
|
||||
captcha_method = "browser" # 打码方式: yescaptcha/browser/personal/remote_browser
|
||||
captcha_method = "remote_browser" # 打码方式: yescaptcha/browser/personal/remote_browser
|
||||
browser_recaptcha_settle_seconds = 1.0 # 打码完成额外稳态等待,速度优先可设 1.0
|
||||
browser_launch_background = true # browser 打码默认后台启动(最小化/避免抢占前台)
|
||||
yescaptcha_api_key = "" # YesCaptcha API密钥
|
||||
yescaptcha_base_url = "https://api.yescaptcha.com"
|
||||
remote_browser_base_url = "" # 远程有头打码服务地址
|
||||
remote_browser_api_key = "" # 远程有头打码服务 API Key
|
||||
remote_browser_timeout = 60 # 远程有头打码请求超时(秒)
|
||||
remote_browser_base_url = "http://127.0.0.1:8060" # 本地 token 池服务地址
|
||||
remote_browser_api_key = "" # 本地 token 池服务 API Key
|
||||
remote_browser_timeout = 35 # 远程有头打码请求超时(秒)
|
||||
|
||||
@@ -116,6 +116,13 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
await concurrency_manager.initialize(tokens)
|
||||
|
||||
if config.captcha_method == "remote_browser":
|
||||
try:
|
||||
warmed_projects = await flow_client.prefill_remote_browser_for_tokens(tokens, action="IMAGE_GENERATION")
|
||||
print(f"✓ Remote browser pool prefill started for {warmed_projects} project(s)")
|
||||
except Exception as e:
|
||||
print(f"⚠ Remote browser pool prefill failed: {e}")
|
||||
|
||||
# Start file cache cleanup task
|
||||
await generation_handler.file_cache.start_cleanup_task()
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class FlowClient:
|
||||
"flow_request_fingerprint",
|
||||
default=None
|
||||
)
|
||||
self._remote_browser_prefill_last_sent: Dict[str, float] = {}
|
||||
|
||||
# Default "real browser" headers (Android Chrome style) to reduce upstream 4xx/5xx instability.
|
||||
# These will be applied as defaults (won't override caller-provided headers).
|
||||
@@ -1980,6 +1981,7 @@ class FlowClient:
|
||||
method="POST",
|
||||
path=f"/api/v1/sessions/{session_id}/error",
|
||||
json_data={"error_reason": error_reason or error_message or "upstream_error"},
|
||||
timeout_override=2,
|
||||
)
|
||||
except Exception as e:
|
||||
debug_logger.log_warning(f"[reCAPTCHA RemoteBrowser] 上报 error 失败: {e}")
|
||||
@@ -2000,6 +2002,7 @@ class FlowClient:
|
||||
method="POST",
|
||||
path=f"/api/v1/sessions/{session_id}/finish",
|
||||
json_data={"status": "success"},
|
||||
timeout_override=2,
|
||||
)
|
||||
except Exception as e:
|
||||
debug_logger.log_warning(f"[reCAPTCHA RemoteBrowser] 上报 finish 失败: {e}")
|
||||
@@ -2027,6 +2030,17 @@ class FlowClient:
|
||||
|
||||
return base_url, api_key, timeout
|
||||
|
||||
@staticmethod
|
||||
def _build_remote_browser_http_timeout(read_timeout: float) -> httpx.Timeout:
|
||||
read_value = max(3.0, float(read_timeout))
|
||||
write_value = min(10.0, max(3.0, read_value))
|
||||
return httpx.Timeout(
|
||||
connect=2.5,
|
||||
read=read_value,
|
||||
write=write_value,
|
||||
pool=2.5,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _sync_json_http_request(
|
||||
method: str,
|
||||
@@ -2040,7 +2054,7 @@ class FlowClient:
|
||||
request_method = (method or "GET").upper()
|
||||
request_kwargs: Dict[str, Any] = {
|
||||
"headers": req_headers,
|
||||
"timeout": timeout,
|
||||
"timeout": FlowClient._build_remote_browser_http_timeout(timeout),
|
||||
}
|
||||
|
||||
if payload is not None:
|
||||
@@ -2051,7 +2065,7 @@ class FlowClient:
|
||||
try:
|
||||
# remote_browser 控制面只需要稳定传输 JSON,不需要浏览器指纹伪装。
|
||||
# 使用 httpx 可以避免 curl_cffi 在当前环境下 POST body 被吞掉。
|
||||
async with httpx.AsyncClient(follow_redirects=True) as session:
|
||||
async with httpx.AsyncClient(follow_redirects=False, trust_env=False) as session:
|
||||
response = await session.request(
|
||||
method=request_method,
|
||||
url=url,
|
||||
@@ -2103,25 +2117,72 @@ class FlowClient:
|
||||
|
||||
return payload
|
||||
|
||||
async def prefill_remote_browser_pool(
|
||||
self,
|
||||
project_id: str,
|
||||
action: str = "IMAGE_GENERATION",
|
||||
token_id: Optional[int] = None,
|
||||
*,
|
||||
cooldown_seconds: float = 8.0,
|
||||
) -> bool:
|
||||
"""让本地 remote_browser 服务提前开始补池,尽量把取 token 等待搬到前面。"""
|
||||
if config.captcha_method != "remote_browser":
|
||||
return False
|
||||
|
||||
normalized_project = str(project_id or "").strip()
|
||||
normalized_action = str(action or "IMAGE_GENERATION").strip() or "IMAGE_GENERATION"
|
||||
if not normalized_project:
|
||||
return False
|
||||
|
||||
cache_key = f"{normalized_project}|{normalized_action}|{int(token_id or 0)}"
|
||||
now_value = time.monotonic()
|
||||
last_sent = float(self._remote_browser_prefill_last_sent.get(cache_key, 0.0) or 0.0)
|
||||
if (now_value - last_sent) < max(0.5, float(cooldown_seconds)):
|
||||
return False
|
||||
|
||||
try:
|
||||
await self._call_remote_browser_service(
|
||||
method="POST",
|
||||
path="/api/v1/prefill",
|
||||
json_data={
|
||||
"project_id": normalized_project,
|
||||
"action": normalized_action,
|
||||
"token_id": token_id,
|
||||
},
|
||||
timeout_override=3,
|
||||
)
|
||||
self._remote_browser_prefill_last_sent[cache_key] = now_value
|
||||
return True
|
||||
except Exception as e:
|
||||
debug_logger.log_warning(f"[reCAPTCHA RemoteBrowser] prefill 失败: {e}")
|
||||
return False
|
||||
|
||||
async def prefill_remote_browser_for_tokens(self, tokens: List[Any], action: str = "IMAGE_GENERATION") -> int:
|
||||
if config.captcha_method != "remote_browser":
|
||||
return 0
|
||||
|
||||
unique_projects: List[str] = []
|
||||
seen_projects = set()
|
||||
for token in tokens or []:
|
||||
project_id = str(getattr(token, "current_project_id", "") or "").strip()
|
||||
if not project_id or project_id in seen_projects:
|
||||
continue
|
||||
seen_projects.add(project_id)
|
||||
unique_projects.append(project_id)
|
||||
|
||||
warmed = 0
|
||||
for project_id in unique_projects:
|
||||
if await self.prefill_remote_browser_pool(project_id, action=action):
|
||||
warmed += 1
|
||||
return warmed
|
||||
|
||||
def _resolve_remote_browser_solve_timeout(self, action: str) -> int:
|
||||
base_timeout = max(5, int(config.remote_browser_timeout or 60))
|
||||
action_name = str(action or "").strip().upper()
|
||||
|
||||
if action_name == "VIDEO_GENERATION":
|
||||
expected = max(
|
||||
int(getattr(config, "video_timeout", 1500) or 1500) + 180,
|
||||
int(getattr(config, "flow_timeout", 120) or 120) + 240,
|
||||
900,
|
||||
)
|
||||
else:
|
||||
expected = max(
|
||||
int(getattr(config, "image_timeout", 300) or 300) + 180,
|
||||
int(getattr(config, "upsample_timeout", 300) or 300) + 120,
|
||||
int(getattr(config, "flow_timeout", 120) or 120) + 180,
|
||||
300,
|
||||
)
|
||||
|
||||
return max(base_timeout, min(expected, 3600))
|
||||
# 这里只是拿 reCAPTCHA token,不应该跟整条生成链路共用数百秒级超时。
|
||||
target_timeout = 45 if action_name == "VIDEO_GENERATION" else 35
|
||||
return max(12, min(base_timeout, target_timeout))
|
||||
|
||||
async def _get_recaptcha_token(
|
||||
self,
|
||||
|
||||
@@ -927,6 +927,12 @@ class GenerationHandler:
|
||||
progress=22,
|
||||
response_extra={"project_id": project_id},
|
||||
)
|
||||
prefill_action = "IMAGE_GENERATION" if generation_type == "image" else "VIDEO_GENERATION"
|
||||
await self.flow_client.prefill_remote_browser_pool(
|
||||
project_id=project_id,
|
||||
action=prefill_action,
|
||||
token_id=token.id,
|
||||
)
|
||||
|
||||
# 5. 根据类型处理
|
||||
generation_pipeline_started_at = time.time()
|
||||
|
||||
Reference in New Issue
Block a user