feature: архитектура v2 — api + очередь + worker, удержание окна браузера (#78)
- Сервис api (FastAPI, без Chrome): POST /v2/tasks, GET /v2/tasks/{id} (доступ по cookie
сессии), DELETE /v2/sessions/current; /v1/solve — прежний контракт поверх очереди.
- Сервис worker (dramatiq + RabbitMQ): слоты браузера, окна сессий между задачами
(browser_idle_timeout 0–120 мин, простой — TTL ключа в Redis), heartbeat.
- Маршрутизация задач сессии в персональную очередь воркера с открытым окном.
- Redis: задачи, результаты (RESULT_TTL_HOURS), окна, воркеры. Прокси и ключ 2captcha
в Redis не сохраняются.
- Движок: AsyncStealthySession.run() — действия на текущей странице без навигации.
- Два образа (robotex-api, robotex-worker), compose с rabbitmq и redis,
make up/down/logs/smoke, релиз собирает все образы.
- Тесты: api + worker в одном процессе (fakeredis), настоящий воркер dramatiq на
StubBroker, браузерные тесты на новой схеме, smoke для запущенного стенда.
- Документация: README, API (v2), Deployment, Usage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fe509c25c3
commit
7b511d5d38
38 files changed
+2142
-545
No files matched your search
@@ -0,0 +1,36 @@
|
||||
"""Запросы и ответы для лога без секретов клиента: креды прокси, ключ 2captcha, вводимые значения,
|
||||
значения cookies, html и скриншот."""
|
||||
import json
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from api.schemas import SolveRequest
|
||||
|
||||
|
||||
def mask_proxy(proxy_url: str | None) -> str | None:
|
||||
if not proxy_url:
|
||||
return proxy_url
|
||||
parts = urlsplit(proxy_url)
|
||||
return f"{parts.scheme}://{'***@' if parts.username else ''}{parts.hostname}:{parts.port}"
|
||||
|
||||
|
||||
def safe_request_dump(payload: SolveRequest) -> str:
|
||||
data = payload.model_dump(exclude_none=True)
|
||||
data["proxy"] = mask_proxy(payload.proxy)
|
||||
if payload.captcha:
|
||||
data["captcha"] = "***"
|
||||
if payload.cookies:
|
||||
data["cookies"] = [c.get("name") for c in payload.cookies]
|
||||
for act in data["actions"]:
|
||||
if act["type"] == "fill" and "value" in act:
|
||||
act["value"] = "***"
|
||||
return json.dumps(data, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def safe_response_dump(result: dict) -> str:
|
||||
data = dict(result)
|
||||
if data.get("cookies"):
|
||||
data["cookies"] = [c.get("name") for c in data["cookies"]]
|
||||
for key in ("html", "screenshot"):
|
||||
if data.get(key):
|
||||
data[key] = f"<{len(data[key])} chars>"
|
||||
return json.dumps(data, indent=2, ensure_ascii=False)
|
||||
Reference in new issue
Block a user