Вызывайте API CAPTCHA из Spring Boot

Автор: solvethree4 мин на чтение

Зарегистрируйте один серверный бин CaptchaClient, создайте задачу один раз, затем опрашивайте её по сохранённому ID. Приведённый ниже вспомогательный класс использует синхронные вызовы Spring RestClient и возвращает ответы в виде Gson JsonObject.

На этой странице

Добавьте зависимости и зарегистрируйте вспомогательный класс

Предлагаемый вспомогательный класс использует Java 21+, Spring Web 7.0.10 и Gson 2.15.0. В существующем проекте Spring Boot сохраните совместимую версию Spring, управляемую Boot, вместо того чтобы переопределять только spring-web ради соответствия примеру. Добавьте Gson в зависимости, если приложение ещё не использует его.

Spring · серверный сервис RestClient
// Java 21+, Spring Web 7.0.10 and Gson 2.15.0; use your Boot dependency management.
// Save as CaptchaClient.java in your application's package.
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.http.HttpClient;
import java.time.Duration;
import org.springframework.http.MediaType;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

public final class CaptchaClient {
    private final RestClient api;
    public CaptchaClient(String key) {
        var transport = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
            .followRedirects(HttpClient.Redirect.NEVER).build();
        var factory = new JdkClientHttpRequestFactory(transport);
        factory.setReadTimeout(Duration.ofSeconds(25));
        api = RestClient.builder().baseUrl("https://api.solvethree.com/v1")
            .requestFactory(factory).defaultHeader("Authorization", "Bearer " + key).build();
    }
    public JsonObject create(String site, String proxy, String requestId) {
        var input = new JsonObject();
        input.addProperty("site", site);
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", requestId); // Save before calling create.
        String body = api.post().uri("/tasks?waitMs=0").contentType(MediaType.APPLICATION_JSON)
            .body(input.toString()).retrieve().body(String.class);
        return JsonParser.parseString(body).getAsJsonObject();
    }
    public JsonObject get(String id) {
        if (!id.matches("[A-Za-z0-9_-]+")) throw new IllegalArgumentException("Invalid task ID");
        String body = api.get().uri("/tasks/{id}?waitMs=15000", id)
            .retrieve().body(String.class);
        return JsonParser.parseString(body).getAsJsonObject();
    }
}
// Construct once with System.getenv("SOLVETHREE_API_KEY") in a server-side @Bean.
// Call create once, save its id, then get(id) until ready/failed or your deadline.
// RestClient throws on HTTP errors; keep the saved requestId and any known task id.

Сохраните класс как CaptchaClient.java в пакете приложения. В серверном фабричном методе @Bean создайте его один раз, используя переменную окружения SOLVETHREE_API_KEY. При запуске проверяйте, что ключ настроен. Вспомогательный класс добавляет его в заголовок авторизации Bearer; не помещайте ключ в браузерный код.

Повторно используйте этот бин через внедрение в конструктор. RestClient можно использовать из разных потоков, но каждый запрос блокирует вызывающий поток. Не вызывайте эти методы в цикле событий WebFlux. Синхронный клиент описан в документации Spring по REST-клиентам.

Транспорт использует тайм-аут подключения 10 секунд и тайм-аут чтения 25 секунд, а перенаправления отключены. Ответы читаются как строки и разбираются с помощью Gson, а не преобразуются в типизированные объекты ответов.

Задайте сайт, действие и прокси

Перед вызовом create настройте активный профиль целевого сайта в рабочем пространстве. Отправка имени хоста и действия не создаёт этот профиль. Если сайт ещё не настроен, см. настройку профиля сайта.

  • Передавайте в site имя хоста, а не полный URL страницы. В запросе reCAPTCHA нет полей url и type.
  • Во вспомогательном классе значение action жёстко задано как login. Измените его или сделайте параметром метода, если для нужного запроса используется другое действие.
  • Передавайте один URL прокси. API требует ровно одно из полей proxy или proxyCandidates; этот вспомогательный класс реализует вариант с одним прокси.
  • Вспомогательный класс не передаёт recaptchaType, поэтому API использует значение по умолчанию enterprise-v3.

Создайте задачу один раз, затем опрашивайте её по сохранённому ID

  1. 1.

    Сохраните ID запроса перед созданием задачи

    Выберите requestId длиной 1–64 символа из букв ASCII, цифр, знаков подчёркивания или дефисов. Сохраните его вместе с входными данными задачи до вызова create. Он позволит восстановить ту же задачу, если ответ на запрос создания будет потерян.

  2. 2.

    Вызовите create и сохраните полученный id

    Вызовите метод create бина, передав имя хоста, прокси и сохранённый ID запроса. Он отправляет один POST-запрос на /v1/tasks с waitMs=0. Сохраните полученный id; для последующих обращений используйте этот ID задачи, а не ID запроса.

  3. 3.

    Вызывайте get, пока задача не перейдёт в конечный статус

    Передавайте сохранённый ID задачи в get. При каждом обращении запрашивается waitMs=15000, а вызов блокируется в ожидании ответа. Продолжайте опрос при pending или solving, соблюдая предельный срок, заданный на уровне приложения. Остановитесь при ready или failed. Тайм-ауты HTTP ограничивают отдельные запросы, а не весь цикл опроса.

  4. 4.

    Читайте решение только после готовности

    Для reCAPTCHA отправляйте solution.token целевому ресурсу, для которого он предназначен, а не ID задачи. Используйте полученные userAgent и secChUa с тем же прокси, с которым решение было успешно получено. Не подставляйте посторонние браузерные заголовки и не меняйте целевой URL. Готовая задача предоставляет решение, но не подтверждает, что целевой ресурс принял ваш последующий запрос.

Обрабатывайте ошибки HTTP отдельно от ошибок задач

Вызов create завершается по тайм-ауту до получения ID задачи.

Сервер мог создать задачу, даже если клиент не получил ответ.

Попробуйте: Для восстановления повторите тот же запрос задачи с сохранённым requestId. При совпадении входных данных вернётся существующая задача. Не создавайте новый ID запроса, если не собираетесь создавать новую логическую задачу.

Вызов get завершается ошибкой на уровне HTTP.

RestClient.retrieve() выбрасывает исключение при неуспешном HTTP-ответе. Это не означает, что задача имеет статус failed.

Попробуйте: Обработайте ошибку HTTP, прежде чем пытаться прочитать тело успешного ответа. Сохраните известный ID задачи для восстановления вместо создания новой задачи. Выясните причину некорректности входных данных, прежде чем повторять запрос без изменений.

Ответ задачи содержит статус failed.

Прочитайте её errorCode и error; site-unreachable — один из возможных кодов ошибки задачи.

Попробуйте: Прекратите опрос этой задачи и устраните указанную причину, прежде чем запускать новую. Средства за неудачные решения возвращаются; не считайте, что ошибка означает нехватку средств.

Повторное использование ID запроса приводит к конфликту.

Сравните новые входные данные задачи с данными, сохранёнными для этого ID запроса.

Попробуйте: Чтобы восстановить существующую задачу, используйте исходные входные данные. Если вы намеренно меняете входные данные задачи, используйте новый ID запроса.

Пояснения к ошибкам см. в разделе Ошибки CAPTCHA API.

Запустите первую задачу.

Начните с $5 на балансе рабочего пространства.

Создать рабочее пространство
Вызывайте API CAPTCHA из Spring Boot · solvethree