Resuelve reCAPTCHA Enterprise con Rust
Usa la API REST de solvethree desde Rust: crea una tarea, consulta su estado hasta que esté lista y luego envía solution.token al destino usando la identidad del navegador devuelta y el mismo proxy. El siguiente ejemplo usa reqwest, Tokio y serde_json directamente, no un SDK oficial de Rust.
En esta página
Configura el sitio y la solicitud
Primero, crea un perfil activo para el nombre de host de destino en la consola de tu espacio de trabajo. Enviar un nombre de host y una acción no crea un perfil. Consulta la configuración del perfil del sitio si el destino aún no está configurado.
Define estas variables de entorno en el servidor donde se ejecutará el cliente de Rust:
SOLVETHREE_API_KEY: la clave de API de tu espacio de trabajo. Mantenla en el servidor.SOLVETHREE_PROXY: la URL del proxy para la resolución y la posterior solicitud al destino.REQUEST_ID: un ID opcional y estable para esta tarea lógica. Guárdalo antes de enviar la solicitud para poder recuperarla tras una interrupción de la conexión.
| Campo | Qué enviar |
|---|---|
site | El nombre de host de destino. Sustituye accounts.example.com en el ejemplo. |
action | La acción que espera el destino. Sustituye login si es necesario. |
proxy | La única URL de proxy, leída de SOLVETHREE_PROXY. |
requestId | Una clave de idempotencia que contiene entre 1–64 letras ASCII, números, guiones bajos o guiones. |
recaptchaType | Se omite aquí porque su valor predeterminado es enterprise-v3. |
No añadas los campos type ni url a esta solicitud de reCAPTCHA. Se requiere exactamente uno de los campos proxy o proxyCandidates; el ejemplo usa proxy para que el envío al destino pueda usar esa misma conexión.
Crea el cliente de Rust
Coloca el archivo de dependencias en Cargo.toml y el cliente en src/main.rs de un proyecto binario de Cargo. Tras definir las variables de entorno y cambiar el nombre de host y la acción de destino, ejecuta el proyecto con Cargo. Las dependencias fijan reqwest en la versión 0.13.5.
[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"] }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())
}
}
}El cliente realiza una sola solicitud de creación a POST /v1/tasks, autenticándose con un token Bearer. Conserva el ID de tarea devuelto y usa GET /v1/tasks/:id para las solicitudes de estado posteriores.
Dos límites impiden que el cliente espere indefinidamente: el tiempo de espera de reqwest limita cada solicitud HTTP a 30 segundos, mientras que el de Tokio limita la creación y las consultas periódicas en conjunto a 180 segundos. Son decisiones del lado del cliente, no garantías de finalización del servicio. Las redirecciones se deshabilitan mediante ClientBuilder de reqwest.
Solo pending y solving permiten continuar el bucle de consultas periódicas. ready devuelve el resultado, failed detiene el proceso con un error y un estado inesperado también lo detiene en lugar de seguir consultando indefinidamente. La ruta de éxito imprime el ID de tarea, no el token.
Recupera una solicitud interrumpida sin otra resolución
El ejemplo conserva requestId y el ID de tarea devuelto en memoria y los imprime cuando se produce un error HTTP, de transporte, de análisis o de vencimiento del plazo. No los guarda de forma persistente tras la terminación del proceso. Para recuperar la tarea tras un reinicio, guarda el ID de solicitud y los datos de entrada originales antes de la creación, y luego guarda el ID de tarea en cuanto llegue.
- 1.
Si tienes un ID de tarea, recupera esa tarea
Reanuda con GET /v1/tasks/:id en lugar de crear otra tarea. El punto de entrada normal del ejemplo siempre crea una tarea; la ruta de reinicio debe usar el ID guardado.
- 2.
Si solo tienes un ID de solicitud, recupera la creación
Envía la misma solicitud de tarea con ese mismo requestId. Esto devuelve la tarea existente. Cambiar los datos de entrada al reutilizar el ID provoca un conflicto.
- 3.
Gestiona el estado recuperado
Sigue consultando las tareas en estado pending o solving. Usa una solución en estado ready, o detente si el estado es failed y examina errorCode y error. Que se agote el tiempo de espera local no demuestra por sí solo que la tarea remota haya fallado.
Los fallos HTTP y los fallos de tarea requieren una gestión distinta. El cliente comprueba si las respuestas HTTP son no satisfactorias antes de analizar una respuesta de éxito. En cambio, un fallo terminal de tarea llega con el estado failed; las resoluciones fallidas se reembolsan. El mensaje de fallo del ejemplo solo incluye errorCode, así que examina también error en la respuesta de la tarea al diagnosticarlo. Consulta la gestión de errores de la API antes de reintentar con los mismos datos de entrada.
Envía el token con su identidad de navegador
El ejemplo se detiene cuando la tarea está lista; no envía la solicitud de la aplicación al destino. Esa solicitud necesita solution.token, no el ID de tarea. Usa la identidad devuelta en userAgent y secChUa a través del proxy original y envía la solicitud al destino previsto, sin mezclar valores de otra sesión de navegador.
Una tarea lista significa que se ha generado una solución, no que el destino haya aceptado la solicitud de tu aplicación. Añade el envío específico al destino en el comentario señalado de la ruta de éxito; consulta la guía de integración de reCAPTCHA Enterprise para obtener indicaciones sobre la entrega del token.
Fuentes y referencia de la API
Ejecuta tu primera tarea.
Empieza con $5 de crédito en tu espacio de trabajo.