Files
robotex/src/tasks/broker.py
T
protokeyandClaude Opus 5.5 1a4bd8d672 feature: API-ключи клиентов и журнал использования в Postgres (#4, #17)
- Postgres в compose; пакет db (SQLAlchemy async): clients, windows, requests.
- Все эндпоинты, кроме /health, — только с ключом в X-API-Key, иначе 401; без
  выпущенных ключей доступа нет. В базе — только sha256 ключа.
- Задача и окно сессии привязаны к клиенту: чужой ключ их не видит.
- Воркер пишет журнал: окно (открыто, последнее обращение, закрыто) и задачу
  (в очереди, начало, конец, итог, данные без секретов). Сбой записи не роняет задачу.
- python -m db.cli create|list|revoke|stats; make key-create/key-list/key-revoke/stats.
- smoke и loadtest с ключом.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:48:48 +04:00

54 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Брокер dramatiq поверх RabbitMQ (#78) и сообщения, которыми api ставит работу воркерам.
Каждый воркер слушает общую очередь `robotex` и персональную `robotex.worker.<id>`. В персональную
api отправляет задачи сессии, чьё окно открыто на этом воркере (#83), иначе — в общую. Сообщение
адресуется actor'у по имени, поэтому api не импортирует код воркера (и Chrome ему не нужен).
`RABBITMQ_URL=stub://` — брокер в памяти для тестов.
"""
import os
import dramatiq
from dramatiq.brokers.stub import StubBroker
from dramatiq.middleware import AsyncIO
COMMON_QUEUE = "robotex"
RUN_TASK = "run_task"
CLOSE_WINDOW = "close_window"
def personal_queue(worker_id: str) -> str:
return f"{COMMON_QUEUE}.worker.{worker_id}"
def make_broker(url: str) -> dramatiq.Broker:
if url.startswith("stub://"):
broker = StubBroker()
else:
from dramatiq.brokers.rabbitmq import RabbitmqBroker
# confirm_delivery: enqueue возвращается, только когда RabbitMQ принял сообщение
broker = RabbitmqBroker(url=url, confirm_delivery=True)
broker.add_middleware(AsyncIO())
return broker
broker = make_broker(os.getenv("RABBITMQ_URL", "amqp://guest:guest@localhost:5672/"))
dramatiq.set_broker(broker)
def task_message(
task_id: str, session_id: str, payload: dict, idle_timeout: int, queue: str = COMMON_QUEUE,
meta: dict | None = None,
) -> dramatiq.Message:
"""payload — SolveRequest/TaskRequest.model_dump(): в нём прокси и ключ 2captcha, поэтому
он живёт только в сообщении, а не в Redis. meta — для журнала: client_id, api (v1/v2)."""
return dramatiq.Message(
queue_name=queue, actor_name=RUN_TASK,
args=(task_id, session_id, payload, idle_timeout, meta or {}), kwargs={}, options={},
)
def close_message(session_id: str, queue: str) -> dramatiq.Message:
return dramatiq.Message(queue_name=queue, actor_name=CLOSE_WINDOW, args=(session_id,), kwargs={}, options={})