Introdução
O Hyper é a biblioteca HTTP de baixo nível mais importante do ecossistema Rust. Ele implementa o protocolo HTTP/1.1 e HTTP/2 de forma correta, segura e extremamente performática. Praticamente todo o ecossistema web do Rust é construído sobre o Hyper: o Axum usa Hyper como servidor, o Reqwest usa Hyper como cliente, e o Tonic usa Hyper para transportar gRPC.
Diferente de frameworks de alto nível como Axum ou Actix Web, o Hyper expõe os detalhes do protocolo HTTP, dando a você controle total sobre como as requisições e respostas são processadas. Isso o torna ideal para construir frameworks customizados, proxies, load balancers e qualquer componente de infraestrutura HTTP.
Por que entender o Hyper?
- Fundação do ecossistema: Axum, Reqwest e Tonic são construídos sobre ele
- Performance extrema: um dos servidores HTTP mais rápidos em qualquer linguagem
- Controle total: acesso a cada detalhe do protocolo HTTP
- HTTP/2 nativo: suporte completo a HTTP/2, incluindo multiplexação
- Streaming: processamento de corpos de requisição/resposta como streams
- Modular: pode ser usado como servidor, cliente, ou ambos
Instalação
Adicione o Hyper ao seu Cargo.toml:
[dependencies]
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
http-body-util = "0.1"
tokio = { version = "1", features = ["full"] }
bytes = "1"
As features do Hyper são granulares, permitindo incluir apenas o que você precisa:
[dependencies]
# Apenas servidor HTTP/1
hyper = { version = "1", features = ["http1", "server"] }
# Apenas cliente HTTP/2
hyper = { version = "1", features = ["http2", "client"] }
# Tudo incluído
hyper = { version = "1", features = ["full"] }
O pacote hyper-util fornece utilitários adicionais como TokioIo para integrar com Tokio e TokioExecutor para execução de tasks.
Uso Básico
Servidor HTTP simples
O exemplo mais básico de um servidor HTTP com Hyper:
use std::convert::Infallible;
use std::net::SocketAddr;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
// Handler que processa cada requisição
async fn handler(
req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let resposta = match (req.method(), req.uri().path()) {
(&hyper::Method::GET, "/") => {
Response::new(Full::new(Bytes::from("Olá, mundo!")))
}
(&hyper::Method::GET, "/saude") => {
Response::new(Full::new(Bytes::from(r#"{"status": "ok"}"#)))
}
_ => {
let mut resp = Response::new(Full::new(Bytes::from("Não encontrado")));
*resp.status_mut() = StatusCode::NOT_FOUND;
resp
}
};
Ok(resposta)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("Servidor rodando em http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(handler))
.await
{
eprintln!("Erro ao servir conexão: {:?}", err);
}
});
}
}
Servidor com estado compartilhado
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
// Estado compartilhado entre todas as conexões
struct AppState {
contador_requisicoes: AtomicU64,
nome_app: String,
}
async fn handler(
state: Arc<AppState>,
_req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let contagem = state.contador_requisicoes.fetch_add(1, Ordering::Relaxed) + 1;
let corpo = format!(
"App: {} | Requisição #{}\n",
state.nome_app, contagem
);
Ok(Response::new(Full::new(Bytes::from(corpo))))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let state = Arc::new(AppState {
contador_requisicoes: AtomicU64::new(0),
nome_app: "Meu Servidor Hyper".to_string(),
});
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("Servidor rodando em http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let state = state.clone();
tokio::task::spawn(async move {
let service = service_fn(move |req| {
let state = state.clone();
async move { handler(state, req).await }
});
if let Err(err) = http1::Builder::new()
.serve_connection(io, service)
.await
{
eprintln!("Erro: {:?}", err);
}
});
}
}
Roteamento manual
use std::convert::Infallible;
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use hyper::{Method, Request, Response, StatusCode};
async fn roteador(
req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
match (req.method(), req.uri().path()) {
// GET /
(&Method::GET, "/") => ok_json(r#"{"mensagem": "Bem-vindo à API"}"#),
// GET /usuarios
(&Method::GET, "/usuarios") => {
ok_json(r#"[{"id": 1, "nome": "Maria"}, {"id": 2, "nome": "João"}]"#)
}
// POST /usuarios
(&Method::POST, "/usuarios") => {
// Ler o corpo da requisição
let corpo = req.collect().await
.map(|c| c.to_bytes())
.unwrap_or_default();
let texto = String::from_utf8_lossy(&corpo);
let resposta = format!(r#"{{"criado": true, "dados": {}}}"#, texto);
let mut resp = Response::new(Full::new(Bytes::from(resposta)));
*resp.status_mut() = StatusCode::CREATED;
resp.headers_mut().insert(
hyper::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
Ok(resp)
}
// GET /usuarios/:id (roteamento simples por prefixo)
(&Method::GET, path) if path.starts_with("/usuarios/") => {
let id = &path["/usuarios/".len()..];
ok_json(&format!(r#"{{"id": {}, "nome": "Usuário {}"}}"#, id, id))
}
// 404 para todas as outras rotas
_ => {
let mut resp = Response::new(Full::new(Bytes::from(
r#"{"erro": "Rota não encontrada"}"#,
)));
*resp.status_mut() = StatusCode::NOT_FOUND;
Ok(resp)
}
}
}
fn ok_json(corpo: &str) -> Result<Response<Full<Bytes>>, Infallible> {
let mut resp = Response::new(Full::new(Bytes::from(corpo.to_string())));
resp.headers_mut().insert(
hyper::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
Ok(resp)
}
Recursos Avançados
Servidor HTTP/2
use std::net::SocketAddr;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http2;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::net::TcpListener;
async fn handler(
_req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, std::convert::Infallible> {
Ok(Response::new(Full::new(Bytes::from("HTTP/2 funcionando!"))))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("Servidor HTTP/2 em http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http2::Builder::new(TokioExecutor::new())
.serve_connection(io, service_fn(handler))
.await
{
eprintln!("Erro HTTP/2: {:?}", err);
}
});
}
}
Servidor que suporta HTTP/1 e HTTP/2 simultaneamente
use std::net::SocketAddr;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto;
use tokio::net::TcpListener;
async fn handler(
req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, std::convert::Infallible> {
let versao = format!("{:?}", req.version());
let corpo = format!("Versão HTTP: {}", versao);
Ok(Response::new(Full::new(Bytes::from(corpo))))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("Servidor HTTP/1+2 em http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = auto::Builder::new(TokioExecutor::new())
.serve_connection(io, service_fn(handler))
.await
{
eprintln!("Erro: {:?}", err);
}
});
}
}
Cliente HTTP com Hyper
use http_body_util::{BodyExt, Empty};
use hyper::body::Bytes;
use hyper::Request;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Client::builder(TokioExecutor::new())
.build_http();
// Construir requisição manualmente
let req = Request::builder()
.method("GET")
.uri("http://httpbin.org/get")
.header("User-Agent", "hyper-client/1.0")
.body(Empty::<Bytes>::new())?;
let resp = client.request(req).await?;
println!("Status: {}", resp.status());
println!("Headers: {:#?}", resp.headers());
// Ler o corpo completo
let corpo = resp.into_body().collect().await?.to_bytes();
println!("Corpo: {}", String::from_utf8_lossy(&corpo));
Ok(())
}
Streaming de corpo de resposta
use std::convert::Infallible;
use bytes::Bytes;
use http_body_util::StreamBody;
use hyper::body::Frame;
use hyper::{Request, Response};
use tokio_stream::StreamExt;
async fn streaming_handler(
_req: Request<hyper::body::Incoming>,
) -> Result<Response<StreamBody<impl tokio_stream::Stream<Item = Result<Frame<Bytes>, Infallible>>>>, Infallible> {
// Criar um stream que envia dados gradualmente
let stream = tokio_stream::iter(0..10).map(|i| {
let chunk = format!("Chunk {}: dados do servidor\n", i);
Ok(Frame::data(Bytes::from(chunk)))
});
let body = StreamBody::new(stream);
Ok(Response::new(body))
}
Middleware manual (wrapping services)
use std::convert::Infallible;
use std::time::Instant;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Response};
// Wrapper que adiciona logging
async fn com_logging(
req: Request<hyper::body::Incoming>,
handler: impl Fn(Request<hyper::body::Incoming>) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Response<Full<Bytes>>, Infallible>> + Send>>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let metodo = req.method().clone();
let caminho = req.uri().path().to_string();
let inicio = Instant::now();
let resposta = handler(req).await;
let duracao = inicio.elapsed();
if let Ok(ref resp) = resposta {
println!(
"{} {} -> {} ({:?})",
metodo,
caminho,
resp.status(),
duracao
);
}
resposta
}
Graceful shutdown
use std::net::SocketAddr;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use tokio::signal;
async fn handler(
_req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, std::convert::Infallible> {
Ok(Response::new(Full::new(Bytes::from("Servidor ativo!"))))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("Servidor em http://{}", addr);
// Canal para sinalizar shutdown
let (tx_shutdown, _) = tokio::sync::broadcast::channel::<()>(1);
loop {
tokio::select! {
resultado = listener.accept() => {
let (stream, _) = resultado?;
let io = TokioIo::new(stream);
let mut rx = tx_shutdown.subscribe();
tokio::task::spawn(async move {
let conn = http1::Builder::new()
.serve_connection(io, service_fn(handler));
tokio::pin!(conn);
tokio::select! {
resultado = &mut conn => {
if let Err(err) = resultado {
eprintln!("Erro: {:?}", err);
}
}
_ = rx.recv() => {
println!("Encerrando conexão graciosamente...");
conn.as_mut().graceful_shutdown();
// Aguardar conclusão das requisições em andamento
if let Err(err) = conn.await {
eprintln!("Erro no shutdown: {:?}", err);
}
}
}
});
}
_ = signal::ctrl_c() => {
println!("\nRecebido Ctrl+C, encerrando...");
let _ = tx_shutdown.send(());
break;
}
}
}
println!("Servidor encerrado.");
Ok(())
}
Boas Práticas
1. Use Hyper apenas quando necessário
Na maioria dos casos, frameworks de alto nível como Axum ou Actix Web são mais apropriados. Use Hyper diretamente quando:
- Estiver construindo um framework web
- Precisar de controle total sobre o protocolo HTTP
- Estiver construindo um proxy ou load balancer
- Precisar de performance extrema e quiser eliminar overhead
2. Sempre trate erros de conexão
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
async fn aceitar_conexoes(listener: TcpListener) {
loop {
match listener.accept().await {
Ok((stream, addr)) => {
println!("Nova conexão de: {}", addr);
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
let resultado = http1::Builder::new()
.serve_connection(io, service_fn(|_req| async {
Ok::<_, std::convert::Infallible>(
hyper::Response::new(
http_body_util::Full::new(
hyper::body::Bytes::from("OK")
)
)
)
}))
.await;
if let Err(err) = resultado {
// Diferenciar tipos de erro
if err.is_incomplete_message() {
// Cliente desconectou - normal
} else if err.is_parse() {
eprintln!("Erro de parse HTTP de {}: {}", addr, err);
} else {
eprintln!("Erro de conexão de {}: {}", addr, err);
}
}
});
}
Err(e) => {
eprintln!("Erro ao aceitar conexão: {}", e);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
}
}
3. Configure limites de conexão
use hyper::server::conn::http1;
fn configurar_http1() -> http1::Builder {
let mut builder = http1::Builder::new();
// Limitar tamanho do cabeçalho (padrão: ~400KB)
builder.max_buf_size(8 * 1024); // 8KB
// Habilitar keep-alive
builder.keep_alive(true);
// Habilitar pipeline HTTP/1.1
builder.pipeline_flush(true);
builder
}
4. Prefira tipos concretos a trait objects
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Response;
// Bom: tipo concreto, sem alocação dinâmica
fn resposta_rapida() -> Response<Full<Bytes>> {
Response::new(Full::new(Bytes::from("Rápido!")))
}
// Evitar em hot paths: BoxBody usa alocação dinâmica
// use http_body_util::combinators::BoxBody;
// fn resposta_lenta() -> Response<BoxBody<Bytes, hyper::Error>> { ... }
5. Use tower::Service para composição
// O Hyper se integra com Tower para middleware composável
// Veja a página sobre Tower para detalhes
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Exemplo conceitual de composição com Tower
fn criar_service() {
let _service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
// .layer(RateLimitLayer::new(100, Duration::from_secs(1)))
// .service(meu_handler)
;
}
Exemplos Práticos
Servidor HTTP completo com roteamento e JSON
use std::collections::HashMap;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use hyper::header::CONTENT_TYPE;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Nota {
id: u64,
titulo: String,
conteudo: String,
}
struct AppState {
notas: Mutex<HashMap<u64, Nota>>,
proximo_id: Mutex<u64>,
}
impl AppState {
fn new() -> Self {
Self {
notas: Mutex::new(HashMap::new()),
proximo_id: Mutex::new(1),
}
}
}
fn json_response(status: StatusCode, corpo: &str) -> Response<Full<Bytes>> {
let mut resp = Response::new(Full::new(Bytes::from(corpo.to_string())));
*resp.status_mut() = status;
resp.headers_mut().insert(CONTENT_TYPE, "application/json".parse().unwrap());
resp
}
async fn roteador(
state: Arc<AppState>,
req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let metodo = req.method().clone();
let caminho = req.uri().path().to_string();
let resposta = match (metodo, caminho.as_str()) {
// Listar todas as notas
(Method::GET, "/notas") => {
let notas = state.notas.lock().unwrap();
let lista: Vec<&Nota> = notas.values().collect();
let json = serde_json::to_string(&lista).unwrap();
json_response(StatusCode::OK, &json)
}
// Criar nota
(Method::POST, "/notas") => {
let corpo = req.collect().await.unwrap().to_bytes();
match serde_json::from_slice::<serde_json::Value>(&corpo) {
Ok(dados) => {
let mut proximo_id = state.proximo_id.lock().unwrap();
let id = *proximo_id;
*proximo_id += 1;
let nota = Nota {
id,
titulo: dados["titulo"]
.as_str()
.unwrap_or("Sem título")
.to_string(),
conteudo: dados["conteudo"]
.as_str()
.unwrap_or("")
.to_string(),
};
let json = serde_json::to_string(¬a).unwrap();
state.notas.lock().unwrap().insert(id, nota);
json_response(StatusCode::CREATED, &json)
}
Err(_) => json_response(
StatusCode::BAD_REQUEST,
r#"{"erro": "JSON inválido"}"#,
),
}
}
// Obter nota por ID
(Method::GET, path) if path.starts_with("/notas/") => {
let id_str = &path["/notas/".len()..];
match id_str.parse::<u64>() {
Ok(id) => {
let notas = state.notas.lock().unwrap();
match notas.get(&id) {
Some(nota) => {
let json = serde_json::to_string(nota).unwrap();
json_response(StatusCode::OK, &json)
}
None => json_response(
StatusCode::NOT_FOUND,
r#"{"erro": "Nota não encontrada"}"#,
),
}
}
Err(_) => json_response(
StatusCode::BAD_REQUEST,
r#"{"erro": "ID inválido"}"#,
),
}
}
// Deletar nota
(Method::DELETE, path) if path.starts_with("/notas/") => {
let id_str = &path["/notas/".len()..];
match id_str.parse::<u64>() {
Ok(id) => {
let mut notas = state.notas.lock().unwrap();
if notas.remove(&id).is_some() {
json_response(StatusCode::OK, r#"{"removida": true}"#)
} else {
json_response(
StatusCode::NOT_FOUND,
r#"{"erro": "Nota não encontrada"}"#,
)
}
}
Err(_) => json_response(
StatusCode::BAD_REQUEST,
r#"{"erro": "ID inválido"}"#,
),
}
}
// Rota padrão
_ => json_response(
StatusCode::NOT_FOUND,
r#"{"erro": "Rota não encontrada"}"#,
),
};
Ok(resposta)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let state = Arc::new(AppState::new());
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await?;
println!("API de Notas rodando em http://{}", addr);
println!("Rotas:");
println!(" GET /notas - Listar notas");
println!(" POST /notas - Criar nota");
println!(" GET /notas/:id - Obter nota");
println!(" DELETE /notas/:id - Deletar nota");
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let state = state.clone();
tokio::task::spawn(async move {
let service = service_fn(move |req| {
let state = state.clone();
async move { roteador(state, req).await }
});
if let Err(err) = http1::Builder::new()
.serve_connection(io, service)
.await
{
eprintln!("Erro de conexão: {:?}", err);
}
});
}
}
Proxy reverso simples
use std::convert::Infallible;
use std::net::SocketAddr;
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::client::legacy::Client;
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::net::TcpListener;
async fn proxy(
client: Client<hyper_util::client::legacy::connect::HttpConnector, Full<Bytes>>,
destino: String,
req: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
let caminho = req.uri().path().to_string();
let query = req.uri().query().map(|q| format!("?{}", q)).unwrap_or_default();
let uri_destino = format!("{}{}{}", destino, caminho, query);
println!("Proxy: {} -> {}", req.uri(), uri_destino);
// Construir nova requisição para o destino
let proxy_req = Request::builder()
.method(req.method())
.uri(&uri_destino)
.body(Full::new(Bytes::new()))
.unwrap();
match client.request(proxy_req).await {
Ok(resp) => {
let status = resp.status();
let corpo = resp.into_body().collect().await
.map(|c| c.to_bytes())
.unwrap_or_default();
let mut resposta = Response::new(Full::new(corpo));
*resposta.status_mut() = status;
Ok(resposta)
}
Err(err) => {
eprintln!("Erro no proxy: {}", err);
let mut resp = Response::new(Full::new(Bytes::from("Erro no proxy")));
*resp.status_mut() = StatusCode::BAD_GATEWAY;
Ok(resp)
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let destino = "http://httpbin.org".to_string();
let client = Client::builder(TokioExecutor::new()).build_http();
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let listener = TcpListener::bind(addr).await?;
println!("Proxy rodando em http://{} -> {}", addr, destino);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
let client = client.clone();
let destino = destino.clone();
tokio::task::spawn(async move {
let service = service_fn(move |req| {
let client = client.clone();
let destino = destino.clone();
async move { proxy(client, destino, req).await }
});
if let Err(err) = http1::Builder::new()
.serve_connection(io, service)
.await
{
eprintln!("Erro: {:?}", err);
}
});
}
}
Comparação com Alternativas
| Característica | Hyper | Axum | Actix Web | Warp |
|---|---|---|---|---|
| Nível | Baixo nível | Alto nível | Alto nível | Alto nível |
| Roteamento | Manual | Embutido | Embutido | Embutido |
| Middleware | Manual/Tower | Tower | Próprio | Filters |
| HTTP/2 | Nativo | Via Hyper | Nativo | Via Hyper |
| Ergonomia | Baixa | Alta | Alta | Média |
| Performance | Máxima | Excelente | Excelente | Excelente |
| Casos de uso | Infra/frameworks | APIs/web apps | APIs/web apps | APIs/web apps |
- Hyper vs Axum: Axum é construído sobre Hyper e adiciona roteamento, extratores e middleware. Use Axum para aplicações web e Hyper para componentes de infraestrutura.
- Hyper vs Actix Web: Actix Web usa seu próprio runtime e modelo de atores. Hyper é mais flexível e integra melhor com o ecossistema Tokio.
- Hyper vs Warp: Warp também usa Hyper internamente, mas com uma API baseada em filtros composáveis. Warp tem menos manutenção ativa que Axum.
Conclusão
O Hyper é a fundação do ecossistema HTTP em Rust. Mesmo que você nunca o use diretamente, entender como ele funciona vai ajudá-lo a debugar problemas, otimizar performance e tomar decisões arquiteturais melhores ao usar frameworks como Axum ou Reqwest.
Para aplicações web típicas, prefira usar Axum (que é construído sobre Hyper). Reserve o uso direto do Hyper para cenários que exigem controle total do protocolo HTTP, como proxies, load balancers e frameworks customizados.
Próximos passos
- Aprenda Axum para construir APIs web de forma ergonômica sobre o Hyper
- Explore Tower para adicionar middleware composável ao Hyper
- Estude Reqwest para o lado do cliente HTTP (também construído sobre Hyper)
- Veja Tonic para gRPC, que também é construído sobre Hyper