Spring BootからCAPTCHA APIを呼び出す

著者:solvethree読了時間:1分

サーバー側で CaptchaClient Beanを1つ登録し、タスクを一度だけ作成して、保存したIDでポーリングします。以下のヘルパーはSpringの同期 RestClient 呼び出しを使用し、Gsonの JsonObject レスポンスを返します。

このページの内容

依存関係を追加してヘルパーを登録する

付属のヘルパーはJava 21+、Spring Web 7.0.10、Gson 2.15.0を使用します。既存のSpring Bootプロジェクトでは、サンプルに合わせて spring-web だけを上書きせず、Bootが管理する互換性のあるSpringバージョンを維持してください。アプリケーションに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認証ヘッダーに追加します。ブラウザー側のコードには含めないでください。

コンストラクターインジェクションでこのBeanを再利用します。RestClient はスレッド間で再利用できますが、各リクエストは呼び出し元のスレッドをブロックします。WebFluxのイベントループ上でこれらのメソッドを呼び出さないでください。同期クライアントについては、SpringのRESTクライアントのドキュメントを参照してください。

通信には10秒の接続タイムアウトと25秒の読み取りタイムアウトを設定し、リダイレクトは行いません。レスポンスは文字列として読み取り、Gsonで解析します。型付きのレスポンスオブジェクトにはマッピングしません。

サイト、アクション、プロキシを設定する

create を呼び出す前に、ワークスペースで対象サイトの有効なプロファイルを設定してください。ホスト名とアクションを送信しても、そのプロファイルは作成されません。サイトがまだ設定されていない場合は、サイトプロファイルの設定を参照してください。

  • site にはページの完全なURLではなく、ホスト名を渡してください。reCAPTCHAリクエストには url フィールドも type フィールドもありません。
  • ヘルパーでは action が login に固定されています。目的のリクエストで別のアクションを使う場合は、この値を変更するか、メソッドのパラメーターにしてください。
  • プロキシURLを1つ渡してください。APIでは proxy または proxyCandidates のいずれか一方だけが必要です。このヘルパーは単一プロキシの方式を実装しています。
  • このヘルパーは recaptchaType を省略するため、APIはデフォルトの enterprise-v3 を使用します。

一度だけ作成し、保存したタスクIDでポーリングする

  1. 1.

    作成前にリクエストIDを保存する

    ASCIIの英字、数字、アンダースコア、ハイフンからなる1–64文字の requestId を選びます。create を呼び出す前に、タスクの入力値とともに保存してください。作成レスポンスが失われた場合でも、同じタスクを復旧できます。

  2. 2.

    createを呼び出し、返されたidを保存する

    Beanの create メソッドにホスト名、プロキシ、保存したリクエストIDを渡して呼び出します。waitMs=0 を指定し、/v1/tasks にPOSTを1回送信します。返された id を保存してください。以降の取得には、リクエストIDではなく、このタスクIDを使用します。

  3. 3.

    タスクが終了ステータスになるまでgetを呼び出す

    保存したタスクIDを get に渡します。各取得リクエストでは waitMs=15000 を指定し、レスポンスを待つ間はブロックします。pending または solving の場合は、アプリケーションレベルの期限内で続行します。ready または failed で停止します。HTTPタイムアウトが制限するのは個々のリクエストであり、ポーリングループ全体ではありません。

  4. 4.

    準備完了時にのみソリューションを読み取る

    reCAPTCHAでは、タスクIDではなく solution.token を本来の送信先に送信します。返された userAgent と secChUa を、解決に成功したときと同じプロキシで使用してください。無関係なブラウザーヘッダーに置き換えたり、送信先URLを変更したりしないでください。準備完了のタスクはソリューションを提供しますが、その後のリクエストを送信先が受け入れたことを保証するものではありません。

HTTPエラーとタスクの失敗を分けて処理する

タスクIDを受け取る前にcreateの呼び出しがタイムアウトする。

クライアントがレスポンスを受信していなくても、サーバー側ではタスクが作成されている可能性があります。

試してみましょう: 保存した requestId を使い、同じタスクリクエストを再送して復旧します。入力値が一致すれば、既存のタスクが返されます。新しい論理タスクを作成する意図がない限り、新しいリクエストIDを生成しないでください。

getの呼び出しがHTTP層で失敗する。

RestClient.retrieve() は、成功以外のHTTPレスポンスで例外をスローします。これはタスクのステータスが failed であることとは異なります。

試してみましょう: 成功時のペイロードを読み取ろうとする前に、HTTPエラーを処理してください。別のタスクを作成せず、復旧に使えるよう既知のタスクIDを保持します。無効な入力値は、そのまま再試行する前に原因を調べてください。

タスクのレスポンスのステータスが failed になっている。

その errorCode と error を確認してください。タスクのエラーコードには site-unreachable などがあります。

試してみましょう: そのタスクのポーリングを停止し、別のタスクを開始する前に、報告された原因に対処してください。解決に失敗した場合は返金されます。失敗がクレジット不足を意味するとは限りません。

リクエストIDを再利用すると競合が発生する。

新しいタスクの入力値を、そのリクエストIDに対して保存した入力値と比較してください。

試してみましょう: 既存のタスクを復旧するには、元の入力値を使用します。意図的に異なるタスク入力値を使う場合は、新しいリクエストIDを使用してください。

エラーの意味については、CAPTCHA APIエラーを参照してください。

最初のタスクを実行しましょう。

$5のワークスペースクレジットで始められます。

ワークスペースを作成
Spring BootからCAPTCHA APIを呼び出す · solvethree