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
- Criar Servidor HTTP — construa o lado servidor
- Parse JSON em Rust — processe as respostas JSON
- Serializar Struct para JSON — prepare dados para enviar
- Async/Await Básico — entenda o modelo assíncrono
- Conectar ao PostgreSQL — persista dados de APIs
- Documentação da crate: reqwest