Peticiones HTTP GET y POST en Rust con reqwest

Para hacer peticiones HTTP en Rust el crate dominante es reqwest. Su forma habitual es asíncrona, pero trae una variante bloqueante que evita tener que aprender async para hacer una simple consulta. Esta página usa esa variante.

La dependencia

cargo add reqwest --features blocking,json
cargo add serde --features derive
cargo add serde_json
[dependencies]
reqwest = { version = "0.13.5", features = ["blocking", "json"] }
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
No desactives las características por defecto. Si añades reqwest con --no-default-features para dejarlo ligero, te quedas sin TLS y cualquier dirección https:// deja de funcionar. Las características por defecto incluyen el soporte de TLS, así que conviene dejarlas y añadir blocking y json encima.

Un GET y leer el JSON de respuesta

Lo bueno de combinar reqwest con serde es que la respuesta se convierte directamente en una estructura tuya, sin pasar por texto:

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Respuesta {
    url: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let r = reqwest::blocking::get("https://httpbin.org/get")?;
    println!("estado: {}", r.status());

    let datos: Respuesta = r.json()?;
    println!("url consultada: {}", datos.url);
    Ok(())
}
salidaestado: 200 OK
url consultada: https://httpbin.org/get

La estructura solo declara los campos que te interesan. El JSON de httpbin trae bastantes más y serde los ignora sin protestar, que es justo lo que quieres al consumir una API ajena: si mañana añaden campos, tu programa sigue funcionando.

Un POST con cuerpo JSON

Para enviar datos hace falta un cliente, porque la petición se construye por partes:

use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cuerpo = HashMap::new();
    cuerpo.insert("usuario", "ada");
    cuerpo.insert("accion", "alta");

    let cliente = reqwest::blocking::Client::new();
    let r = cliente.post("https://httpbin.org/post").json(&cuerpo).send()?;

    println!("estado: {}", r.status());
    let eco: serde_json::Value = r.json()?;
    println!("el servidor recibió: {}", eco["json"]);
    Ok(())
}
salidaestado: 200 OK
el servidor recibió: {"accion":"alta","usuario":"ada"}

El método .json(&cuerpo) hace dos cosas: convierte el valor a JSON y pone la cabecera Content-Type correcta. httpbin devuelve lo que recibió, por eso la última línea muestra el cuerpo de vuelta.

Lo que se usa a diario

Qué quieres Cómo
GET sencillo reqwest::blocking::get(url)?
El cuerpo como texto .text()?
El cuerpo convertido a tu tipo .json::<T>()?
Comprobar que fue bien .error_for_status()?
Cabeceras propias .header("Authorization", v)
Parámetros de consulta .query(&[("q", "rust")])
Tiempo límite Client::builder().timeout(...)
Un 404 no es un error de red. send() devuelve Ok aunque el servidor conteste 404 o 500: desde el punto de vista de reqwest la petición salió bien, el servidor respondió. Para que un código de error se convierta en un Err que puedas propagar con ?, hay que encadenar .error_for_status()?. Es la distinción entre fallo de transporte y respuesta negativa, y conviene no mezclarlas.

El código de esta página se compiló y ejecutó con rustc 1.96.1 y reqwest 0.13.5 antes de publicarla, con peticiones reales a httpbin.org; las salidas están copiadas de la ejecución. Documentación oficial: reqwest en docs.rs.