Fazer Requisição HTTP em Rust

Aprenda como fazer requisição HTTP em Rust com reqwest. GET, POST, headers, JSON body e chamadas assíncronas com tokio. Exemplos completos.

Fazer Requisição HTTP em Rust

A crate reqwest é a biblioteca HTTP mais popular do ecossistema Rust. Ela suporta requisições síncronas e assíncronas, headers personalizados, envio de JSON, upload de arquivos e muito mais.

Dependências

Cargo.toml:

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Requisição GET simples

A forma mais básica de fazer uma requisição HTTP:

use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // GET simples — retorna o corpo como String
    let resposta = reqwest::get("https://httpbin.org/get")
        .await?
        .text()
        .await?;

    println!("Resposta:\n{}", resposta);

    Ok(())
}

Saída (simplificada):

Resposta:
{
  "args": {},
  "headers": {
    "Host": "httpbin.org",
    ...
  },
  "url": "https://httpbin.org/get"
}

GET com verificação de status

Sempre verifique o status da resposta em código de produção:

use reqwest;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let resposta = reqwest::get("https://httpbin.org/status/200").await?;

    println!("Status: {}", resposta.status());
    println!("Content-Type: {:?}", resposta.headers().get("content-type"));

    if resposta.status().is_success() {
        let corpo = resposta.text().await?;
        println!("Sucesso! Corpo: {} bytes", corpo.len());
    } else {
        println!("Erro HTTP: {}", resposta.status());
    }

    // error_for_status() converte códigos 4xx/5xx em erro
    let resultado = reqwest::get("https://httpbin.org/status/404")
        .await?
        .error_for_status();

    match resultado {
        Ok(_resp) => println!("Tudo certo!"),
        Err(e) => println!("Erro esperado: {}", e),
    }

    Ok(())
}

Saída:

Status: 200 OK
Content-Type: Some("text/html; charset=utf-8")
Sucesso! Corpo: 0 bytes
Erro esperado: HTTP status client error (404 Not Found) for url (https://httpbin.org/status/404)

POST com corpo JSON

Envie dados JSON no corpo da requisição:

use reqwest;
use serde::{Deserialize, Serialize};
use std::error::Error;

#[derive(Serialize)]
struct NovoPedido {
    produto: String,
    quantidade: u32,
    preco_unitario: f64,
}

#[derive(Debug, Deserialize)]
struct Resposta {
    json: serde_json::Value,
    url: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let pedido = NovoPedido {
        produto: "Notebook Rust Edition".to_string(),
        quantidade: 2,
        preco_unitario: 4599.90,
    };

    let client = reqwest::Client::new();

    let resposta = client
        .post("https://httpbin.org/post")
        .json(&pedido) // serializa automaticamente para JSON
        .send()
        .await?
        .json::<Resposta>()
        .await?;

    println!("URL: {}", resposta.url);
    println!("Dados enviados: {:#}", resposta.json);

    Ok(())
}

Saída:

URL: https://httpbin.org/post
Dados enviados: {
  "preco_unitario": 4599.9,
  "produto": "Notebook Rust Edition",
  "quantidade": 2
}

Headers personalizados

Adicione headers de autenticação, tipo de conteúdo e outros:

use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Headers por requisição
    let client = reqwest::Client::new();

    let resposta = client
        .get("https://httpbin.org/headers")
        .header(USER_AGENT, "MeuApp/1.0")
        .header(AUTHORIZATION, "Bearer meu-token-aqui")
        .header("X-Custom-Header", "valor-personalizado")
        .send()
        .await?
        .text()
        .await?;

    println!("Headers enviados:\n{}", resposta);

    // Headers padrão para todas as requisições do client
    let mut headers_padrao = HeaderMap::new();
    headers_padrao.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    headers_padrao.insert(USER_AGENT, HeaderValue::from_static("MeuApp/1.0"));

    let client = reqwest::Client::builder()
        .default_headers(headers_padrao)
        .timeout(std::time::Duration::from_secs(10))
        .build()?;

    let resp = client
        .get("https://httpbin.org/get")
        .send()
        .await?;

    println!("\nStatus com headers padrão: {}", resp.status());

    Ok(())
}

Saída (simplificada):

Headers enviados:
{
  "headers": {
    "Authorization": "Bearer meu-token-aqui",
    "User-Agent": "MeuApp/1.0",
    "X-Custom-Header": "valor-personalizado",
    ...
  }
}

Status com headers padrão: 200 OK

Requisições paralelas

Faça múltiplas requisições ao mesmo tempo com join!:

use reqwest;
use std::error::Error;
use tokio;

async fn buscar_url(url: &str) -> Result<(String, u16, usize), Box<dyn Error + Send + Sync>> {
    let resp = reqwest::get(url).await?;
    let status = resp.status().as_u16();
    let corpo = resp.text().await?;
    Ok((url.to_string(), status, corpo.len()))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let urls = vec![
        "https://httpbin.org/get",
        "https://httpbin.org/ip",
        "https://httpbin.org/user-agent",
        "https://httpbin.org/headers",
    ];

    // Executar todas as requisições em paralelo
    let mut handles = Vec::new();
    for url in urls {
        handles.push(tokio::spawn(buscar_url(url)));
    }

    println!("{:<45} {:>6} {:>10}", "URL", "Status", "Bytes");
    println!("{}", "-".repeat(63));

    for handle in handles {
        match handle.await? {
            Ok((url, status, tamanho)) => {
                println!("{:<45} {:>6} {:>10}", url, status, tamanho);
            }
            Err(e) => println!("Erro: {}", e),
        }
    }

    Ok(())
}

Saída:

URL                                           Status      Bytes
---------------------------------------------------------------
https://httpbin.org/get                          200        364
https://httpbin.org/ip                           200         32
https://httpbin.org/user-agent                   200         44
https://httpbin.org/headers                      200        220

Versão síncrona (blocking)

Se você não precisa de async, use a feature blocking:

Cargo.toml:

[dependencies]
reqwest = { version = "0.12", features = ["json", "blocking"] }
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // GET síncrono
    let corpo = reqwest::blocking::get("https://httpbin.org/ip")?
        .text()?;

    println!("IP: {}", corpo);

    Ok(())
}

Veja também