Resolva reCAPTCHA Enterprise com Rust

Por solvethree4 min de leitura

Use a API REST do solvethree em Rust: crie uma tarefa, consulte seu status até que esteja pronta e envie solution.token ao destino usando a identidade de navegador retornada e o mesmo proxy. O exemplo abaixo usa reqwest, Tokio e serde_json diretamente — não um SDK oficial para Rust.

Nesta página

Configure o site e a solicitação

Primeiro, crie um perfil ativo para o hostname de destino no console do seu espaço de trabalho. Enviar um hostname e uma ação não cria um perfil. Consulte a configuração de perfil de site se o destino ainda não estiver configurado.

Defina estas variáveis de ambiente no servidor em que o cliente Rust será executado:

  • SOLVETHREE_API_KEY: a chave de API do seu espaço de trabalho. Mantenha-a no servidor.
  • SOLVETHREE_PROXY: a URL do proxy para a resolução e a solicitação subsequente ao destino.
  • REQUEST_ID: um ID opcional e estável para esta tarefa lógica. Salve-o antes de enviar a solicitação para permitir a recuperação após uma interrupção da conexão.
Campos usados pela tarefa reCAPTCHA
Campos usados pela tarefa reCAPTCHA
CampoO que enviar
siteO hostname de destino. Substitua accounts.example.com no exemplo.
actionA ação esperada pelo destino. Substitua login, se necessário.
proxyA URL de proxy única lida de SOLVETHREE_PROXY.
requestIdUma chave de idempotência contendo de 1–64 letras ASCII, números, sublinhados ou hífens.
recaptchaTypeOmitido aqui porque o padrão é enterprise-v3.

Não adicione os campos type ou url a esta solicitação reCAPTCHA. É necessário fornecer exatamente um dos campos proxy ou proxyCandidates; o exemplo usa proxy para que o envio ao destino possa usar a mesma conexão.

Crie o cliente Rust

Coloque o arquivo de dependências em Cargo.toml e o cliente em src/main.rs de um projeto binário Cargo. Após definir as variáveis de ambiente e alterar o hostname de destino e a ação, execute o projeto com Cargo. As dependências fixam reqwest na versão 0.13.5.

Cargo.toml · dependências verificadas
[package]
name = "solvethree-example"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = { version = "=0.13.5", features = ["json"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
Rust · chamadas REST assíncronas com limites
use std::{env, error::Error, time::{Duration, SystemTime, UNIX_EPOCH}};
use reqwest::{Client, redirect::Policy};
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let api = "https://api.solvethree.com/v1";
    let key = env::var("SOLVETHREE_API_KEY")?;
    let request_id = env::var("REQUEST_ID").unwrap_or_else(|_| format!("rust-{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()));
    let input = json!({"site":"accounts.example.com", "action":"login", "proxy":env::var("SOLVETHREE_PROXY")?, "requestId":request_id});
    let client = Client::builder().redirect(Policy::none()).timeout(Duration::from_secs(30)).build()?;
    let mut task_id: Option<String> = None;
    let outcome = tokio::time::timeout(Duration::from_secs(180), async {
        let response = client.post(format!("{api}/tasks?waitMs=0")).bearer_auth(&key).json(&input).send().await?;
        if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
        let mut result: Value = response.json().await?;
        let id = result["id"].as_str().ok_or("Missing task ID")?.to_owned();
        if !id.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') { return Err("Invalid task ID".into()); }
        task_id = Some(id.clone());
        loop {
            match result["status"].as_str() {
                Some("ready") => return Ok::<Value, Box<dyn Error>>(result),
                Some("failed") => return Err(format!("Task failed: {}", result["errorCode"]).into()),
                Some("pending") | Some("solving") => {},
                _ => return Err("Unexpected task status".into()),
            }
            tokio::time::sleep(Duration::from_secs(1)).await;
            let response = client.get(format!("{api}/tasks/{id}?waitMs=15000")).bearer_auth(&key).send().await?;
            if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
            result = response.json().await?;
        }
    }).await;
    match outcome {
        Ok(Ok(result)) => {
            println!("Ready task: {}", result["id"].as_str().unwrap_or("unknown"));
            // Submit result["solution"]["token"] with returned browser headers
            // through the original proxy; do not log the solution.
            Ok(())
        },
        Ok(Err(error)) => {
            eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
            Err(error)
        },
        Err(_) => {
            eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
            Err("Local deadline reached; recover the existing task".into())
        }
    }
}

O cliente faz uma única solicitação de criação para POST /v1/tasks, autenticando-se com um token Bearer. Ele guarda o ID da tarefa retornado e usa GET /v1/tasks/:id nas solicitações de status subsequentes.

Dois limites impedem que o cliente espere indefinidamente: o tempo limite do reqwest limita cada solicitação HTTP a 30 segundos, enquanto o tempo limite do Tokio limita a criação e as consultas, juntas, a 180 segundos. Essas são escolhas do lado do cliente, não garantias de conclusão do serviço. Os redirecionamentos são desabilitados pelo ClientBuilder do reqwest.

Apenas pending e solving mantêm o loop de consulta. ready retorna o resultado, failed interrompe com um erro, e um status inesperado também interrompe, em vez de consultar indefinidamente. O fluxo de sucesso exibe o ID da tarefa, não o token.

Recupere uma solicitação interrompida sem outra resolução

O exemplo mantém requestId e o ID da tarefa retornado na memória e os exibe quando ocorre um erro HTTP, de transporte, de análise ou de prazo excedido. Ele não os persiste após o encerramento do processo. Para recuperação após reinicialização, armazene o ID da solicitação e os dados de entrada originais da tarefa antes da criação e armazene o ID da tarefa assim que ele chegar.

  1. 1.

    Se você tem um ID de tarefa, consulte essa tarefa

    Retome com GET /v1/tasks/:id em vez de criar outra tarefa. O ponto de entrada normal do exemplo sempre cria uma tarefa; um fluxo de reinicialização deve usar o ID salvo.

  2. 2.

    Se você tiver apenas um ID de solicitação, recupere a criação

    Envie a mesma solicitação de tarefa com o mesmo requestId. Isso retorna a tarefa existente. Alterar os dados de entrada reutilizando o ID causa um conflito.

  3. 3.

    Trate o status recuperado

    Continue consultando tarefas com status pending ou solving. Use uma solução com status ready ou pare no status failed e inspecione errorCode e error. Um tempo limite local excedido, por si só, não comprova que a tarefa remota falhou.

Falhas HTTP e falhas de tarefa exigem tratamentos diferentes. O cliente verifica respostas HTTP sem sucesso antes de interpretar os dados de sucesso. Já uma falha terminal de tarefa chega com o status failed; resoluções com falha são reembolsadas. A mensagem de falha do exemplo inclui apenas errorCode, portanto, inspecione também error na resposta da tarefa ao diagnosticá-la. Consulte tratamento de erros da API antes de tentar novamente com os mesmos dados de entrada.

Envie o token com sua identidade de navegador

O exemplo para quando a tarefa fica pronta; ele não envia a solicitação da aplicação ao destino. Essa solicitação precisa de solution.token, não do ID da tarefa. Use a identidade retornada em userAgent e secChUa pelo proxy original e envie ao destino pretendido, sem misturar valores de outra sessão de navegador.

Uma tarefa pronta significa que uma solução foi gerada, não que o destino aceitou a solicitação da sua aplicação. Adicione o envio específico do destino no comentário marcado no fluxo de sucesso; consulte o guia de integração do reCAPTCHA Enterprise para orientações sobre o envio do token.

Execute sua primeira tarefa.

Comece com $5 em créditos no espaço de trabalho.

Criar um espaço de trabalho
Resolva reCAPTCHA Enterprise com Rust · solvethree