--- title: Protection description: Защиты ответа, правила повтора, ошибки и расширенный SPI понижения. sources: - eazy_sdk.protection - eazy_sdk.protection.advanced - eazy_sdk_presets.cloudflare - eazy_sdk_presets.recaptcha api_fingerprint: "sha256:7f76e89bf3c7dbc5eb08f7e4ffda98e146813d77b8871503ef482a4d2b655d84" --- `eazy_sdk.protection` содержит прикладной API защит ответа: протоколы решателя и контекста, конструктор `challenge_guard()`, назначения решения, правила повтора и ошибки стадий. Контракты компилятора, реестра и хранения доступны только через `eazy_sdk.protection.advanced`. Там же находятся `ChallengeParseError` и `AmbiguousChallengeError`. ## Устанавливаемые защиты | Символ | Контракт | |---|---| | `ChallengeSolver[TChallenge, TSolution]` | Асинхронный протокол `solve(challenge, context) -> solution` | | `SolveContext` | Операция, неизменяемый контекст ответа, номер попытки, `deadline`, `fetch`, `identity` и `request_headers` | | `ProtectedFetch` | Асинхронный порт `(url, *, method, headers, body) -> NormalizedResponse` поверх транспортной сессии клиента | | `TransportIdentity` | Неизменяемые сведения `user_agent`, `proxy`, `impersonation` с закрытым `repr` и методом `fingerprint()` | | `InstallableProtection` | Структурный объект для `Security.of(...)` и `with_protection()` | | `Guard[TChallenge]` | Защита одним классом: `detect()`, синхронный или асинхронный `solve()`, `self.solution(...)`, параметры класса `scope`, `cache`, `replay`, `headers`, `query`, `body` | | `GuardSolution` | Группа cookies, объявленных заголовков, query, body и `expires_at`, которую создаёт `Guard.solution()` | | `challenge_guard()` | Понижает детектор, решатель, отображение решения, кеш и правило повтора | | `GuardCache` | Значения `"none"`, `"call"`, `"session"` для хранения на совпадение, вызов или сессию до отказа | | `host()`, `operation()` | Области защиты; по умолчанию используется `RequestScope()` для каждого запроса | ```python target class VendorGuard(Guard[VendorChallenge]): scope = host("api.example") def detect(self, response: ResponseContext[object]) -> VendorChallenge | None: return detect_challenge(response) async def solve(self, challenge: VendorChallenge, context: SolveContext) -> GuardSolution: return self.solution(cookies={"clearance": await vendor.solve(challenge)}) config = ClientConfig(security=Security.of(VendorGuard())) ``` Функциональная форма принимает `challenge_guard(detect=..., solver=..., apply=solution_fields(...), name=..., scope=..., cache=..., replay=...)`. Имя по умолчанию берётся из функции детектора или класса-владельца, область охватывает каждый запрос, кеш равен `"none"`. Решателем может быть объект с синхронным или асинхронным `solve()` либо функция `(challenge, context) -> solution`. Обе формы возвращают неизменяемое устанавливаемое описание и не читают ответ или сеть во время настройки. `guards=` и `with_protection()` понижают их одинаково. `client.invalidate_protection(*targets)` удаляет сохранённые решения указанных защит или политик. Вызов без аргументов очищает всё состояние; результат равен числу удалённых записей. Детектор получает `ResponseContext[object]` и даёт `TChallenge | None`. Значение `None` означает отсутствие совпадения. Повреждённую распознанную проверку обозначайте `MalformedChallengeError`. Любая другая ошибка детектора становится `ChallengeDetectionError` и сохраняет исходное исключение в `__cause__`. ## Контекст решения `SolveContext` предоставляет решателю сведения о транспорте и ограниченный доступ к нему, но не раскрывает handler, хранилище cookies или клиент. | Поле | Контракт | |---|---| | `operation` | Идентификатор защищаемой операции | | `response` | Неизменяемый контекст ответа с проверкой; `None` для предварительных и обязательных потоков | | `attempt` | Номер исходной попытки; `0` для обязательного потока | | `deadline` | Срок решения от начала вызова; `None` без timeout, `remaining()` не возвращает отрицательное значение | | `fetch` | `ProtectedFetch`, привязанный к тому же handler, прокси и хранилищу cookies | | `identity` | `TransportIdentity`: публичный `User-Agent`, объявленные прокси и профиль имитации | | `request_headers` | Заголовки фактически отправленного запроса | `fetch` обращается напрямую к handler. Защиты, реакции, повторы, middleware и ограничитель частоты не запускаются, поэтому решатель не входит повторно в вызвавший его конвейер. - Перенаправления не выполняются. Возвращается исходный ответ `3xx`. - Каждый запрос получает полный `timeout` защищаемого вызова. Для общего бюджета решатель читает `context.remaining()`. - Запрос использует cookies сессии, а полученные `Set-Cookie` сохраняются в то же хранилище. - URL должен быть абсолютным. `identity.user_agent` и `Host` добавляются, если вызывающий их не задал. Ошибка транспорта или решателя становится `ChallengeSolveError` с исходным исключением в `__cause__`. Сохранённое решение связано с `identity.fingerprint()`. Перед применением runtime сравнивает этот отпечаток с текущей попыткой. Другой `User-Agent`, прокси или профиль имитации отменяет состояние; следующая проверка решается заново под новой транспортной идентичностью. Вручную созданный `SolveContext` имеет пустую идентичность, а его `fetch` вызывает `ProtectionConfigurationError`. У попытки ровно один источник транспортной идентичности. `user_agent` берётся из публичного `User-Agent` после значений вызывающего, авторизации и зависимостей, но до применения решения. `proxy` и `impersonation` читает `HandlerProfile`. Заголовки защиты не меняют отпечаток, хотя `context.request_headers` показывает фактически отправленные заголовки. ## Назначения решения `solution_fields()` связывает имена назначения с полями типизированного решения. | Параметр | Назначение | |---|---| | `headers={"X-Token": "token"}` | Фиксированный заголовок запроса | | `query={"challenge": "token"}` | Фиксированное поле query | | `cookies={"clearance": "token"}` | Фиксированный cookie запроса | | `body={"captcha.token": "token"}` | Закрытый путь семантического тела | | `cookie_set=solution_cookie_set()` | Динамический набор именованных cookies | Компилятор резервирует все назначения. Конфликт с вызывающим, авторизацией, зависимостью, подписью или другим закрытым writer обнаруживается до решателя и сети. Runtime сначала выбирает и проверяет всю группу решения, затем применяет одно атомарное изменение. `PreparedRequest` не изменяется. ## Доказательства безопасного повтора | Функция | Доказательство | |---|---| | `safe_method(max_replays=1)` | Повтор разрешён только для GET, HEAD и OPTIONS | | `idempotency_key(name, max_replays=1)` | Подготовленный запрос содержит указанный заголовок | | `rejected_before_execution(max_replays=1)` | Протокол доказывает отказ до выполнения | | `rejected_before_origin(max_replays=1)` | Протокол доказывает, что запрос не дошёл до источника | `ReplayPolicy.max_replays` не может быть отрицательным. Нулевой бюджет запрещает повтор проверки. Новая попытка заново выполняет зависимости, авторизацию, проекцию, шифрование, кодирование, подготовку и подпись. ## Управляемое состояние и сессия Повторно используемое решение живёт в одном `ExecutionRuntime`, которым владеет клиент и его handler-сессия. Режимы сессии (`cache="session"` и расширенные until-expiry или until-rejected) сводят параллельные решения одной проверки к одному вызову. `cache="call"` и `cache="none"` оставляют состояние внутри вызова. Ключ состояния включает фактический handler. Закрытие клиента очищает решения и записи согласования. Прокси, `User-Agent`, семейство адресов и профиль имитации задаются при создании handler-сессии. Фабрики клиентов настраивают созданную ими сессию и объявляют параметры в `HandlerProfile`. Для переданной пользователем сессии `proxy=` лишь описывает уже заданное значение. Пользовательский handler сообщает те же сведения через `profile`. Eazy SDK не исследует сессию транспорта и не принимает поставщика идентичности на каждую попытку. При смене транспорта создайте новый handler и клиент; их состояние защиты начинается пустым. Технология решателя не участвует в предварительной проверке. Браузер, JavaScript, WASM и внешний API реализуют один типизированный протокол. До запроса проверяются наличие решателя, назначения, повторяемость тела, доказательство безопасного повтора, конечные бюджеты и точность `HandlerProfile`. ## Ошибки | Стадия | Ошибка | Условие | |---|---|---| | Настройка | `ProtectionConfigurationError` | Повреждена защита или понижение, нет решателя либо конфликтуют назначения | | Обнаружение | `ChallengeDetectionError` | Детектор вызвал неожиданное исключение | | Обнаружение | `ChallengeParseError` из `eazy_sdk.protection.advanced` | Проверка распознана, но данные непригодны; доступны `policy`, `attempt` и причина | | Обнаружение | `AmbiguousChallengeError` из `eazy_sdk.protection.advanced` | Две окончательные политики совпали с равным приоритетом | | Решение | `ChallengeSolveError` | Решатель завершился ошибкой | | Применение | `ChallengeApplicationError` | Некорректно поле или назначение решения | | Повтор | `ReplayDeniedError` | Недостаточно повторяемости, доказательства или бюджета политики | Ошибки стадий содержат политику и номер попытки, сохраняют `__cause__` и не включают тела ответов, значения решения, cookies, учётные данные, прокси или секреты решателя. Бюджет считается отдельно для каждой политики. Runtime ожидает решатели в обоих клиентах. В синхронном `Client` асинхронный `solve()` выполняется в вызывающем потоке без цикла событий, пока не приостанавливается на собственном асинхронном вводе-выводе. Такая приостановка приводит к `SynchronousSuspensionError`: используйте `AsyncClient` либо синхронный решатель. Блокировки согласования не привязаны к циклу событий, поэтому общий для нескольких потоков синхронный клиент решает одну проверку сессии один раз. ## Расширенный SPI понижения `eazy_sdk.protection.advanced` служит единственным путём импорта контрактов фреймворков и сгенерированных политик. Его `__all__` закреплён тестом и содержит 49 имён. - Политики: `ChallengePolicy`, `BeforeCallPolicy`, `challenge_policy()`, `before_call_policy()`. - Сигналы: `ResponseSignal`, `SignalInterception`, `SignalMatch`, `SignalOutcome`, `AmbiguousSignal`, `MalformedSignal`. - Решатели: `SolverRequirement`, `SolverBinding`, `bind_solver()`, `SolverBindings`, `SolverLike`. - Порты контекста: `ProtectedFetch`, `TransportIdentity`. - Операции: `OperationReference` для типизации `acquire` и `verify`. - Закрытые назначения: `PrivateBindings`, `PrivateBinding`, `PrivateBindingTarget`, `PrivateCookieSetBinding`, `SolutionLocation`, `private_bindings()`, `private_header()`, `private_query()`, `private_cookie()`, `private_cookie_set()`, `private_body()`. - Хранение: `ProtectionPersistence`, `ProtectionPersistenceMode`, `ProtectionStateKey`, `ProtectionStateScope`, `per_match()`, `per_attempt()`, `per_call()`, `until_expiry()`, `until_rejected()`, `client_identity()`, `session_identity()`, `GuardCache`. - Повтор: `ReplaySafety`, `BodyReplayPolicy`. - Обязательные потоки: `ProtectionFlow`, `protection_flow()`, `FromProtection`. - Ошибки: `ChallengeParseError`, `AmbiguousChallengeError`. - Значение понижения: `ProtectionBundle`. Записи кеша, реестры блокировок, функции компиляции, арбитраж сигналов и применение решения остаются закрытыми. Прикладной код не должен импортировать расширенный SPI.