Controle de Fluxo em Middleware HTTP
Manipuladores HTTP executam concorrentemente sob carga, então um único pânico não deve travar o processo.
Busque em todas as páginas da documentação
Manipuladores HTTP executam concorrentemente sob carga, então um único pânico não deve travar o processo.
Middleware envolve manipuladores com defer/recover, molda o fluxo de controle de escopo de requisição e decide se deve chamar o próximo manipulador - padrões compartilhados entre chi, gin, echo e o stdlib net/http.
Envolva cada manipulador de requisição para que um recover diferido registre o pânico e retorne HTTP 500.
Use defer para timers por requisição, fechamento de corpo e spans de rastreamento.
Middleware retorna antecipadamente não chamando next, ou escrevendo uma resposta e retornando.
A recuperação de pânico pertence à camada útil mais externa - geralmente middleware de servidor ou roteador.
Emparelhe a recuperação com logging estruturado e IDs de requisição do contexto.
Cartão de receita de referência rápida - pronto para copiar e colar.
package main
import (
"log"
"net/http"
)
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("panic: %v path=%s", rec, r.URL.Path)
http.Error(w, http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", hello)
http.ListenAndServe(":8080", recoverMiddleware(mux))
}Quando usar isso:
defer para fechar corpos de requisição e encerrar spans de observabilidade por requisição.next.package main
import (
"context"
"log"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func timeoutMiddleware(d time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), d)
defer cancel()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Recoverer) // middleware defer/recover do chi
r.Use(timeoutMiddleware(2 * time.Second))
r.Get("/boom", func(w http.ResponseWriter, r *http.Request) {
panic("developer mistake")
})
r.Get("/ok", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("fine"))
})
log.Println(http.ListenAndServe(":8080", r))
}O que isso demonstra:
Recoverer do Chi aplica o padrão defer/recover padrão em todas as rotas.timeoutMiddleware chama cancel() diferido para liberar recursos do timer quando a requisição termina.r.WithContext sem globais./boom resulta em 500; /ok ainda serve normalmente.net/http; o pânico desfaz apenas essa goroutine.http.Handler - o controle passa linearmente, a menos que o middleware retorne antecipadamente.defer recover deve estar na mesma função que invoca o manipulador (ou chama next.ServeHTTP).Recovery() com pilhas defer semelhantes e rastros de pilha opcionais.recover são inofensivas, mas redundantes - escolha uma recuperação externa.Requisição ─► Logging ─► Recover ─► Auth ─► Manipulador
│ │ │
│ │ └─ retorna 401 (ignora manipulador)
│ └─ defer recover em pânico
└─ defer log latência
| Framework | Helper de Recuperação | Notas |
|---|---|---|
| stdlib | defer recover escrito manualmente | Controle total, sem dependência |
| chi | middleware.Recoverer | Integra-se à cadeia r.Use |
| gin | gin.Recovery() | O motor padrão inclui logger + recuperação |
| echo | middleware.Recover() | Logging de rastros de pilha configurável |
// Interrupção de autenticação: não chama next quando não autorizado
func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}debug.Stack().Use.defer body.Close() ausente - r.Body vaza conexões em erros. Correção: defer r.Body.Close() em manipuladores ou middleware compartilhado após o roteamento.http.Error com o status apropriado para falhas esperadas.r.Context() em chamadas de I/O, ou os timeouts nunca dispararão. Correção: passe o contexto para http.NewRequestWithContext, chamadas de banco de dados e stubs gRPC.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Middleware Recovery() do Framework | Bootstrap rápido com gin/echo/chi | Você precisa de métricas de pânico personalizadas |
Pânico do http.Server no manipulador | Nunca para produção | - |
| Supervisor de processo apenas | Workers de lote | Servidores HTTP de longa duração |
| Retornos de erro em vez de pânico | Validação de negócios | - |
| Interceptadores gRPC para RPC unário | Serviços gRPC | Manipuladores HTTP simples |
Fora do manipulador, tipicamente no início da cadeia.
A colocação externa captura pânicos de middleware internos também.
Não - ele converte um pânico em desenrolamento em uma resposta 500.
O cliente deve tentar novamente; a goroutine do servidor continua servindo outras requisições.
Sim - feche corpos, descarregue buffers, encerre spans.
Mantenha os defers leves; evite trabalho pesado em defer em caminhos quentes.
Gin registra a pilha e retorna 500 de forma semelhante ao middleware personalizado.
Ajuste com gin.RecoveryWithWriter para roteamento de logs.
O middleware do Echo pode desabilitar ou personalizar a saída da pilha.
Aponte-o para logs estruturados em produção.
Raramente - middleware é linear, não loops aninhados.
Use return para interromper.
Leia de middleware.GetReqID do chi ou defina em middleware personalizado no contexto.
Inclua o ID nos logs de pânico.
Sim - interceptadores unários/de stream espelham a recuperação HTTP.
Use padrões de recuperação do google.golang.org/grpc para servidores RPC.
Somente se os manipuladores respeitarem r.Context().Done().
Chamadas bloqueantes sem contexto ignoram timeouts.
Pânicos na inicialização falham rapidamente antes de aceitar tráfego.
Caminhos de requisição em tempo de execução não devem entrar em pânico por entrada do usuário.
Versões de Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026