---
title: "tower-http: CORS, Timeout e Trace no Axum | Rust Brasil"
url: "https://rustlang.com.br/blog/tower-http-middleware-producao-axum-2026/"
markdown_url: "https://rustlang.com.br/blog/tower-http-middleware-producao-axum-2026.MD"
description: "Guia prático de tower-http em Rust: CORS, TimeoutLayer, TraceLayer, compressão, request ID, limites de corpo, ServeDir e middleware de produção com Axum."
date: "2026-09-18"
author: "Equipe Rust Brasil"
---

# tower-http: CORS, Timeout e Trace no Axum | Rust Brasil

Guia prático de tower-http em Rust: CORS, TimeoutLayer, TraceLayer, compressão, request ID, limites de corpo, ServeDir e middleware de produção com Axum.


**Para colocar uma API [Axum](/ecossistema/axum/) em produção com CORS, timeout, tracing, compressão e limite de corpo sem escrever middleware à mão, use tower-http: um único crate com as camadas HTTP mais usadas do ecossistema [Tower](/ecossistema/tower/).** Adicione `tower-http` ao Cargo.toml com as features de que precisa, empilhe as camadas em um `ServiceBuilder` na ordem certa e o Router ganha comportamento operacional completo em poucas linhas. Este guia mostra o setup, cada camada essencial, a ordem correta de empilhamento, arquivos estáticos com `ServeDir` e os erros comuns.

tower-http já aparece espalhado pelo site — em [serviços resilientes com Tower e Axum](/blog/rust-servicos-resilientes-tower-axum-2026/), em [deploy com Docker Compose](/blog/deploy-axum-docker-compose-postgresql-2026/) e em [autenticação JWT](/blog/autenticacao-jwt-rust-axum-2026/) — mas faltava a página que mostra o crate por inteiro. Esta é ela.

## Resposta rápida: qual camada para cada problema

| Problema em produção | Camada do tower-http | Observação |
|---|---|---|
| Navegador bloqueando a API (preflight falha) | `CorsLayer` | origens, métodos e headers explícitos |
| Handler pendurado travando conexões | `TimeoutLayer` | cliente recebe 408 quando estoura |
| Não saber latência nem status por rota | `TraceLayer` + [tracing](/ecossistema/tracing/) | spans automáticos por requisição |
| Payload gigante derrubando o serviço | `RequestBodyLimitLayer` | rejeite cedo, antes de ler o corpo |
| Respostas grandes sem compressão | `CompressionLayer` | cuidado com streaming/SSE |
| Correlacionar requisições entre serviços | `SetRequestIdLayer` + `PropagateRequestIdLayer` | `X-Request-Id` ponta a ponta |
| Vazar headers sensíveis no log | `SetSensitiveRequestHeadersLayer` | esconde `Authorization`, `Cookie` |
| Servir SPA e assets no mesmo binário | `ServeDir` | com `fallback_service` no Router |
| Header de segurança em toda resposta | `SetResponseHeaderLayer` | HSTS, `X-Content-Type-Options` |

Regra de bolso: se o comportamento é transversal a todas as rotas e tem nome conhecido da indústria (CORS, timeout, rate limit), ele é camada — não código dentro do handler.

## O que é tower-http (e onde ele encaixa)

Tower definiu o vocabulário: `Service` é uma função assíncrona que recebe uma requisição e devolve uma resposta; `Layer` envolve um Service e devolve outro Service com comportamento adicional. tower-http é o crate que usa esse modelo para entregar o middleware HTTP que quase todo serviço real precisa:

- **trace** — spans e eventos por requisição com `tracing`;
- **timeout** — orçamento de tempo por requisição;
- **cors** — política de compartilhamento entre origens;
- **compression** — gzip, brotli, deflate, zstd (por feature);
- **request-id** — gera e propaga identificador de correlação;
- **limit** — limite de tamanho do corpo da requisição;
- **fs** — arquivos estáticos via `ServeDir` e `ServeFile`;
- **set-header / sensitive-headers** — headers de segurança e sigilo;
- **validate-request** — exigir header ou token antes de chegar na rota.

Como Axum, Hyper e Tonic constroem sobre o mesmo trait `Service`, uma camada escrita para um serve aos outros. É por isso que entender tower-http vale em entrevistas de backend e plataforma: a pergunta real é "como você coloca política operacional em um serviço sem espalhar código pelos handlers".

## Setup: Cargo.toml e o ServiceBuilder base

```toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = [
    "trace", "timeout", "cors", "compression-gzip",
    "request-id", "limit", "fs", "set-header",
    "sensitive-headers", "util",
] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v4"] }
```

O stack de produção mínimo em um `main.rs`:

```rust
use std::time::Duration;

use axum::{routing::get, Router};
use tower_http::{
    compression::CompressionLayer,
    cors::CorsLayer,
    limit::RequestBodyLimitLayer,
    request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer},
    timeout::TimeoutLayer,
    trace::TraceLayer,
};
use tower::ServiceBuilder;

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter("info,tower_http=debug")
        .init();

    let app = Router::new()
        .route("/health", get(|| async { "ok" }))
        .layer(
            ServiceBuilder::new()
                .set_x_request_id(MakeRequestUuid::default())
                .propagate_x_request_id()
                .layer(TraceLayer::new_for_http())
                .layer(CorsLayer::permissive()) // troque por política real
                .layer(TimeoutLayer::new(Duration::from_secs(10)))
                .layer(RequestBodyLimitLayer::new(1024 * 1024)) // 1 MiB
                .layer(CompressionLayer::new()),
        );

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

Dez linhas de builder, e o serviço já tem request ID, tracing, CORS, timeout, limite de corpo e compressão. O resto do guia detalha cada peça e a ordem.

## A ordem das layers importa (e é o erro mais comum)

Em `ServiceBuilder`, **a primeira camada declarada é a mais externa**: a requisição atravessa de cima para baixo e a resposta volta de baixo para cima. Ordem recomendada para APIs:

1. **Request ID** (`set_x_request_id`, `propagate_x_request_id`) — o mais externo, para que o ID exista em todos os logs e na resposta final.
2. **`TraceLayer`** — logo depois, observando tudo que acontece dentro, inclusive o resultado do timeout.
3. **`CorsLayer`** — externa o suficiente para que um preflight `OPTIONS` receba resposta correta sem depender das camadas internas.
4. **`TimeoutLayer`** — envolvendo a lógica de negócio; o tempo do handler é o que precisa de orçamento.
5. **`RequestBodyLimitLayer`** — rejeitar payload grande antes de gastar tempo e memória.
6. **`CompressionLayer`** — perto do handler, comprimindo a resposta final.

Sinais de ordem errada na prática:

- preflight CORS quebrando porque o timeout ou um middleware de auth responde antes do CORS;
- logs de tracing sem o request ID (ID declarado dentro do trace);
- timeout que nunca dispara porque foi colocado por dentro de uma camada que faz buffer do corpo.

Se um comportamento depende do outro, o provedor fica mais externo.

## CORS de verdade (não `permissive`)

`CorsLayer::permissive()` serve para protótipo. Em produção, seja explícito:

```rust
use axum::http::{header, HeaderValue, Method};

let cors = CorsLayer::new()
    .allow_origin("https://app.seudominio.com".parse::<HeaderValue>().unwrap())
    .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
    .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]);
```

Para múltiplas origens, `allow_origin` aceita uma lista de `HeaderValue` (e há `allow_origin_fn` para lógica dinâmica, como domínios de preview de deploy). O erro clássico do frontend é ver a API responder, mas o navegador bloquear a leitura: isso é CORS na camada do navegador, resolvido com as respostas de preflight corretas — exatamente o que esta camada emite.

## Timeout: orçamento de tempo por requisição

`TimeoutLayer::new(Duration::from_secs(10))` aplica o mesmo orçamento a todas as rotas. Quando estoura, o cliente recebe **408 Request Timeout** (nas versões recentes do tower-http). Três refinamentos úteis:

- **timeout por rota**: aplique `.layer(TimeoutLayer::new(...))` em um `Router` específico antes de montar o Router geral, com orçamento maior para rotas pesadas;
- **timeout de operação interna**: para uma chamada a banco ou API externa, use `tokio::time::timeout` no handler — camada protege a requisição, `tokio` protege a operação;
- **timeout + retry no cliente**: o lado servidor limita o dano; para chamadas de saída, as políticas de [resiliência com Tower](/blog/rust-servicos-resilientes-tower-axum-2026/) entram em cena.

## TraceLayer: observabilidade sem instrumentar cada handler

`TraceLayer::new_for_http()` cria um span por requisição e registra eventos em `request_started`/`request_finished` (nível DEBUG no crate `tower_http`) com método, caminho, status e latência. Dois ajustes que valem em produção:

```rust
use tower_http::trace::{DefaultMakeSpan, DefaultOnFailure, DefaultOnResponse, TraceLayer};
use tower_http::classify::ServerErrorsFailureClass;

let trace = TraceLayer::new_for_http()
    .make_span_with(
        DefaultMakeSpan::new()
            .level(tracing::Level::INFO)
            .include_headers(false),
    )
    .on_failure(DefaultOnFailure::new().level(tracing::Level::ERROR))
    .on_response(DefaultOnResponse::new().level(tracing::Level::INFO));
```

Combine com [tracing e tracing-subscriber](/ecossistema/tracing/) para JSON estruturado em produção, e com [observabilidade e OpenTelemetry](/blog/rust-opentelemetry-producao-2026/) quando os spans precisarem cruzar serviços. Para rotas parametrizadas, capture o padrão da rota (ex.: `/users/{id}`) em vez do caminho bruto, senão a cardinalidade de métricas explode.

## Corpo, compressão e headers sensíveis

**Limite de corpo.** `RequestBodyLimitLayer::new(1024 * 1024)` rejeita requisições acima de 1 MiB antes da leitura completa — barato e evita que um upload descuidado consuma memória. Para uploads legítimos, aplique a camada apenas no Router de upload.

**Compressão.** `CompressionLayer` com a feature `compression-gzip` (ou brotli/zstd) comprime respostas conforme o `Accept-Encoding`. Cuidado com **SSE e streaming**: compressão pode fazer buffer dos eventos e "congelar" o stream — exclua rotas de eventos da camada de compressão.

**Headers sensíveis.** Antes de logar requisições com `include_headers(true)` ou em proxies internas, esconda credenciais:

```rust
use axum::http::header::{AUTHORIZATION, COOKIE};
use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer;

let sensitive = SetSensitiveRequestHeadersLayer::new([
    AUTHORIZATION,
    COOKIE,
]);
```

**Header de resposta fixo.** Para HSTS ou `X-Content-Type-Options`:

```rust
use axum::http::{HeaderValue, header};
use tower_http::set_header::SetResponseHeaderLayer;

let xcto = SetResponseHeaderLayer::overriding(
    header::X_CONTENT_TYPE_OPTIONS,
    HeaderValue::from_static("nosniff"),
);
```

## Arquivos estáticos: ServeDir no mesmo binário

Com a feature `fs`, o mesmo serviço Axum serve a API e o front:

```rust
use tower_http::services::{ServeDir, ServeFile};

let app = Router::new()
    .route("/api/health", get(|| async { "ok" }))
    .fallback_service(
        ServeDir::new("dist")
            .not_found_service(ServeFile::new("dist/index.html")),
    );
```

O padrão acima serve qualquer asset de `dist/` e devolve o `index.html` para caminhos desconhecidos — o comportamento que SPAs esperam para roteamento no cliente. Em [deploy com Docker Compose](/blog/deploy-axum-docker-compose-postgresql-2026/), isso reduz o número de peças: um binário serve tudo.

## Middleware customizado: `from_fn` antes de implementar Layer

Quando a lógica é específica da aplicação (checar JWT, injetar usuário no request extensions), Axum oferece `middleware::from_fn`, mais simples que implementar um `Layer` completo:

```rust
use axum::{extract::Request, middleware::Next, response::Response};

async fn log_metodo(req: Request, next: Next) -> Response {
    let metodo = req.method().clone();
    let resposta = next.run(req).await;
    tracing::info!(%metodo, status = ?resposta.status(), "requisição processada");
    resposta
}

// no Router: .layer(axum::middleware::from_fn(log_metodo))
```

Implemente um `Layer` de Tower (o modelo de [tower](/ecossistema/tower/)) quando o middleware precisa ser reutilizado entre projetos ou entre Axum e Tonic; use `from_fn` para o resto. Para [autenticação JWT completa](/blog/autenticacao-jwt-rust-axum-2026/) há guia dedicado.

## tower-http com Tonic (gRPC)

As camadas genéricas funcionam para qualquer Service, inclusive gRPC:

```rust
use tower_http::trace::TraceLayer;

let server = tonic::transport::Server::builder()
    .layer(TraceLayer::new_for_grpc())
    .add_service(meu_servico)
    .serve(addr)
    .await?;
```

`TraceLayer::new_for_grpc()` classifica erros pelo código de status gRPC (e não por código HTTP). Timeout, request ID e limite de concorrência aplicam-se igualmente — mesma política de produção para HTTP e gRPC, que é o argumento central do modelo Tower em [carreira backend](/carreira/nicho-web-backend/).

## Erros comuns

- **CORS `permissive` em produção** — funciona, mas abre a API para qualquer origem; a política explícita é três linhas.
- **`OPTIONS` 404 sem CORS externo** — o preflight morre antes de chegar à camada de CORS; revise a ordem do builder.
- **Timeout nunca dispara** — camada colocada por dentro de outra que faz buffer, ou timeout no lugar errado do stack; a camada deve envolver o handler.
- **Compressão congela SSE** — exclua rotas de streaming da `CompressionLayer`.
- **Features faltando no Cargo.toml** — `CorsLayer` sem a feature `cors` não compila; cada grupo de camadas tem feature própria (`compression-gzip`, `fs`, `limit`...).
- **Cardinalidade de métricas estourando** — logar `/users/42` em vez do padrão `/users/{id}` cria uma série temporal por usuário.

## Checklist de produção

1. `set_x_request_id` + `propagate_x_request_id` como camadas mais externas.
2. `TraceLayer` logo abaixo, com nível INFO por span em produção.
3. `CorsLayer` explícito — origens, métodos e headers autorizados.
4. `TimeoutLayer` com orçamento realista (ex.: 10 s geral, 30 s em rotas pesadas).
5. `RequestBodyLimitLayer` compatível com o maior payload legítimo.
6. `CompressionLayer` — exceto em rotas de streaming.
7. `SetSensitiveRequestHeadersLayer` para `Authorization` e `Cookie`.
8. Health check sem auth, sem compressão, com timeout curto.
9. Features do Cargo.toml revisadas — só as usadas.
10. Um teste de integração por camada: preflight, corpo acima do limite, requisição lenta.

## tower-http na carreira

Em [vagas Rust](/vagas/) de backend e plataforma, raramente aparece "tower-http" no título — aparece o que ele entrega: middleware, observabilidade, resiliência, rate limit, APIs operáveis. Um repositório de portfólio com `ServiceBuilder` bem ordenado, spans com request ID e um README explicando a ordem das camadas demonstra maturidade de produção, o que pesa em processos de [empresas que usam Rust](/empresas/). Para aprofundar o modelo por trás, leia a referência de [Tower](/ecossistema/tower/) e o guia de [Tokio](/ecossistema/tokio/).

## Perguntas frequentes

As respostas curtas estão no front matter desta página e cobrem o que é tower-http, a diferença para Tower, CORS no Axum, timeout em rotas, a ordem das layers e o uso com Tonic. Para dúvidas de resiliência além do HTTP — retry, circuit breaker, load shedding — o guia é o de [serviços resilientes com Tower, Axum e Tokio](/blog/rust-servicos-resilientes-tower-axum-2026/).

## Conclusão

tower-http é o atalho honesto do ecossistema Rust: em vez de reimplementar CORS, timeout, tracing e compressão em cada serviço, você empilha camadas testadas sobre o mesmo modelo `Service`/`Layer` que sustenta Axum, Hyper e Tonic. Comece com o `ServiceBuilder` deste guia na ordem recomendada, troque `permissive` por uma política real de CORS e adicione o que faltar quando a operação pedir. O serviço resultante é menor, observável e pronto para as perguntas que uma entrevista de backend faz.
