Cadeias de Middleware & Contexto de Requisição
Middleware envolve http.Handler para executar lógica transversal antes e depois dos seus manipuladores de rota.
Busque em todas as páginas da documentação
Middleware envolve http.Handler para executar lógica transversal antes e depois dos seus manipuladores de rota.
Metadados de escopo de requisição fluem através de r.Context() usando chaves de contexto tipadas e não exportadas.
Uma função de middleware aceita um handler interno e retorna um novo handler que delega a ele.
Cadeias aninham wrappers: o middleware mais externo vê a requisição primeiro e a resposta por último.
r.Context() é o portador padrão para cancelamento, prazos e metadados de requisição.
Use r.WithContext para anexar valores imutavelmente; nunca modifique a requisição no local sem clonar.
Bibliotecas como chi fornecem helpers Use, mas o modelo subjacente é idêntico à composição stdlib.
Cartão de receita de referência rápida - pronto para copiar e colar.
func chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get("X-Request-ID")
if id == "" {
id = uuid.NewString()
}
ctx := context.WithValue(r.Context(), reqIDKey{}, id)
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Quando usar isso:
package main
import (
"context"
"log"
"net/http"
"time"
)
type ctxKey int
const userKey ctxKey = 1
type statusWriter struct {
http.ResponseWriter
code int
}
func (w *statusWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}
func chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
func recoverer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if v := recover(); v != nil {
log.Printf("panic: %v", v)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func authenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userKey, "demo-user")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func accessLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := &statusWriter{ResponseWriter: w, code: http.StatusOK}
start := time.Now()
next.ServeHTTP(sw, r)
log.Printf("%s %s %d %s", r.Method, r.URL.Path, sw.code, time.Since(start))
})
}
func userFrom(ctx context.Context) (string, bool) {
u, ok := ctx.Value(userKey).(string)
return u, ok
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /me", func(w http.ResponseWriter, r *http.Request) {
user, ok := userFrom(r.Context())
if !ok {
http.Error(w, "no user", http.StatusInternalServerError)
return
}
w.Write([]byte(user))
})
handler := chain(mux, recoverer, authenticate, accessLog)
http.ListenAndServe(":8080", handler)
}O que isso demonstra:
statusWriter capturando o status HTTP para logs.http.Handler sobre o handler interno.next.ServeHTTP.r.WithContext retorna uma cópia rasa da requisição com uma nova árvore de contexto.Request -->
recoverer -->
authenticate -->
accessLog -->
mux / handler de negócios
Response <--
O middleware externo deve incluir recuperação e rastreamento; a autenticação fica dentro da recuperação, mas fora da lógica de negócios.
// Equivalente chi (handlers stdlib por baixo):
// r.Use(middleware.Recoverer, middleware.RequestID)
// r.Get("/me", handler)
// Prefira acessadores:
func User(ctx context.Context) (string, bool) {
u, ok := ctx.Value(userKey).(string)
return u, ok
}| Faça | Não Faça |
|---|---|
| Tipos de chave não exportados no pacote de origem | Chaves de string em bibliotecas |
| IDs de requisição, principal de autenticação, local | Estruturas grandes ou parâmetros opcionais |
| Documente chaves no comentário do pacote | Armazene *sql.DB no contexto |
type ctxKey int.r.URL sem Clone - Condição de corrida quando middleware compartilha requisições. Correção: Use r.Clone(ctx) ao alterar URL ou cabeçalhos para sub-roteadores.next.ServeHTTP - Descarta requisições silenciosamente. Correção: Use lint ou teste middleware com httptest afirmando que o corpo foi alcançado.context.Background() downstream - Perde o cancelamento na desconexão do cliente. Correção: Comece a partir de r.Context() para todos os contextos filhos.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Use do chi | Registro de middleware mais limpo | Apenas stdlib sem dependências |
Use do gin / Pre do echo | Vinculação nativa do framework e grupos | Você precisa de exports http.Handler puros |
| Cabeçalhos de Service Mesh | Propagação de identidade entre serviços | Monólito único com autenticação in-process |
| Verificações manuais por handler | Uma ou duas rotas | Regras transversais duplicadas em todos os lugares |
Aplique wrappers de dentro para fora: o último middleware na lista fica mais próximo do handler da rota.
Um helper chain que inverte um slice mantém os locais de chamada legíveis.
Camada mais externa para que todo middleware interno e handler sejam cobertos.
Registre rastros de pilha no lado do servidor; retorne 500 genérico para clientes.
O contexto carrega metadados transversais entre camadas que não compartilham uma estrutura.
Entradas de negócios pertencem a parâmetros de handler tipados ou corpos de requisição.
Sim - o código após next.ServeHTTP é executado na saída.
Capturar o status requer envolver ResponseWriter antes de chamar o próximo.
Sempre derive contextos filhos de r.Context(), não de Background().
Middleware pode anexar prazos mais curtos para sub-chamadas internas.
Elas são aceitáveis se ainda expuserem http.Handler e documentarem as chaves de contexto.
Verifique se os erros de validação de token encerram a execução antes dos handlers.
Crie um handler interno stub que defina um cabeçalho ou escreva um marcador.
Dirija o middleware com httptest.NewRequest e afirme o comportamento de encerramento.
Monte sub-muxes com handlers diferentes e envolvidos: http.StripPrefix mais cadeias distintas.
Grupos de rotas do chi resolvem isso com blocos Route.
Cabeçalhos funcionam para metadados de borda de entrada.
O contexto mantém os acessadores in-process com segurança de tipo sem expor cabeçalhos internos downstream.
Não há limite rígido, mas pilhas profundas obscurecem bugs de ordenação.
Prefira uma pilha padrão documentada mais wrappers específicos de rota com moderação.
Gin carrega seu próprio *gin.Context, mas c.Request.Context() permanece a raiz de cancelamento do stdlib.
Sincronize valores importantes em context.Context para bibliotecas compartilhadas.
Registre /health em um mux separado sem middleware de autenticação, ou ensine o middleware de autenticação a permitir caminhos na lista branca.
Versões da 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 - verifique na compilação), gin (última - verifique na compilação), echo (última - verifique na compilação), google.golang.org/grpc (última - verifique na compilação), sigs.k8s.io/controller-runtime (última - verifique na compilação), kubebuilder (última - verifique na compilação), tinygo (última - verifique os alvos de placa na compilação), wazero (última - verifique na compilação) e golangci-lint (última - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026