Middleware em HTTP
Esta aula ensina a implementar middlewares em HTTP com Go, cobrindo o padrão de middleware, encadeamento, e exemplos práticos de logging e autenticação. Você aprenderá a criar middlewares reutilizáveis que envolvem handlers e a compor pipelines de processamento.
Nesta aula, você vai aprender um dos conceitos mais importantes para construir aplicações web robustas em Go: middlewares. Middlewares são funções que interceptam requisições HTTP antes que elas cheguem ao handler final, permitindo adicionar comportamentos como autenticação, logging, compressão, recuperação de panics e muito mais. Em Go, middlewares são simplesmente funções que recebem um http.Handler e retornam outro http.Handler, criando uma camada de processamento em torno do handler original.
Vamos explorar o padrão de middleware, como encadear vários middlewares de forma elegante e, em seguida, implementaremos exemplos práticos de logging e autenticação. Ao final, você terá uma base sólida para criar pipelines de middleware reutilizáveis e eficientes.
Padrão de middleware
Em Go, um middleware é uma função que recebe um http.Handler e retorna um novo http.Handler. Essa função normalmente faz algum trabalho antes de chamar o handler original, e pode também fazer trabalho depois (como registrar o tempo de resposta). O padrão é:
func meuMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Lógica antes de chamar o próximo handler
// ...
next.ServeHTTP(w, r) // chama o próximo handler
// Lógica depois (opcional)
})
}A função recebe o próximo handler (que pode ser o handler final ou outro middleware) e retorna um novo handler que encapsula a lógica adicional. O tipo http.HandlerFunc é uma adaptação que permite usar uma função comum como um http.Handler.
Para usar um middleware, você o envolve em torno do handler original:
finalHandler := http.HandlerFunc(minhaFuncaoHandler)
http.Handle("/", meuMiddleware(finalHandler))Ou, mais idiomático, usando http.Handle com o middleware aplicado. Esse padrão é simples e poderoso, pois permite combinar múltiplos middlewares em cadeia.
Encadeamento
Encadear middlewares significa aplicar vários em sequência, de modo que cada um envolva o próximo. Por exemplo, se você tem os middlewares logging e autenticacao, pode encadear assim:
handler := autenticacao(logging(finalHandler))A ordem importa: o primeiro middleware na chamada é o mais externo, ou seja, ele executa primeiro e decide se chama o próximo. No exemplo, o autenticacao roda antes do logging. Se a autenticação falhar, o logging não será chamado.
Embora seja possível encadear manualmente, isso fica ilegível com muitos middlewares. Uma abordagem comum é criar um tipo Middleware e uma função que aplica uma lista de middlewares em ordem:
type Middleware func(http.Handler) http.Handler
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}Assim, você pode escrever:
handler := Chain(finalHandler, logging, autenticacao)Essa função aplica os middlewares na ordem fornecida, o que é mais intuitivo: o primeiro da lista é o mais externo. Isso facilita a manutenção e a leitura do código.
Logging e auth
Vamos ver dois exemplos clássicos: um middleware de logging e um de autenticação.
Middleware de logging
O middleware de logging registra informações sobre cada requisição, como método, caminho, duração e status. Para capturar o status, precisamos envolver o ResponseWriter para interceptar a escrita do status.
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Cria um ResponseWriter personalizado para capturar o status
lw := &loggingResponseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(lw, r)
log.Printf("%s %s %d %s", r.Method, r.URL.Path, lw.status, time.Since(start))
})
}
type loggingResponseWriter struct {
http.ResponseWriter
status int
}
func (lw *loggingResponseWriter) WriteHeader(code int) {
lw.status = code
lw.ResponseWriter.WriteHeader(code)
}Esse middleware registra o método, o caminho, o status e o tempo gasto. O tipo loggingResponseWriter sobrescreve WriteHeader para capturar o código de status sem alterar o comportamento original.
Middleware de autenticação
Um middleware de autenticação verifica se o usuário está autenticado. Em um exemplo simples, podemos verificar um cabeçalho Authorization ou um token. Se a autenticação falhar, retornamos http.StatusUnauthorized e não chamamos o próximo handler.
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "segredo123" {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Em uma aplicação real, você validaria um token JWT ou consultaria um banco de dados. Mas esse exemplo ilustra o padrão: se a condição não for satisfeita, interrompe a cadeia e retorna um erro.
Exemplos
Vamos montar um exemplo completo com os middlewares de logging e autenticação. Usaremos o roteador padrão da net/http e aplicaremos os middlewares na ordem desejada.
package main
import (
"log"
"net/http"
"time"
)
func main() {
// Handler final
finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Olá, mundo!"))
})
// Aplica middlewares em cadeia
handler := Chain(finalHandler, Logging, Auth)
http.Handle("/", handler)
log.Println("Servidor rodando na porta 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Middleware type
type Middleware func(http.Handler) http.Handler
func Chain(h http.Handler, mws ...Middleware) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
// Logging middleware
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lw := &loggingResponseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(lw, r)
log.Printf("%s %s %d %s", r.Method, r.URL.Path, lw.status, time.Since(start))
})
}
type loggingResponseWriter struct {
http.ResponseWriter
status int
}
func (lw *loggingResponseWriter) WriteHeader(code int) {
lw.status = code
lw.ResponseWriter.WriteHeader(code)
}
// Auth middleware
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "segredo123" {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Para testar, execute o programa e faça uma requisição com curl. Sem o cabeçalho Authorization, você receberá 401. Com o cabeçalho correto, verá a resposta e o log.
curl -i http://localhost:8080/
# HTTP/1.1 401 Unauthorized
curl -i -H "Authorization: segredo123" http://localhost:8080/
# HTTP/1.1 200 OK
# Olá, mundo!O log mostrará algo como:
2025/01/01 12:00:00 GET / 200 123.456µsEsse exemplo demonstra como os middlewares podem ser compostos de forma limpa e reutilizável.
Boas práticas e observações
Ao trabalhar com middlewares, considere:
- Mantenha cada middleware focado em uma única responsabilidade.
- Use tipos nomeados para middlewares, como
type Middleware func(http.Handler) http.Handler, para melhorar a legibilidade. - Se precisar passar dados entre middlewares e o handler, use o contexto da requisição (
context.Context). - Tenha cuidado com a ordem dos middlewares: por exemplo, o logging deve ser o mais externo para capturar todas as requisições, inclusive as que falharem na autenticação.
- Para aplicações mais complexas, considere usar bibliotecas como
chiougorilla/muxque oferecem suporte a middlewares integrados.
Referências
- Documentação oficial do pacote net/http
- Go Blog: Usando context para cancelamento e valores
- Alex Edwards: Making and Using Middleware
- Mat Ryer: Writing Middleware in Go
- Alice: Painless middleware chaining for Go
- Effective Go
Exercícios
Crie um middleware chamado
RecoverMiddlewareque capture panics no handler e retorne um erro 500 em vez de derrubar o servidor. Teste com um handler que cause panic.✓ Resposta:func RecoverMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("panic: %v", err) http.Error(w, "Erro interno", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }Implemente um middleware que adicione cabeçalhos de segurança (por exemplo,
X-Content-Type-Options: nosniffeX-Frame-Options: DENY) a todas as respostas.✓ Resposta:func SecurityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") next.ServeHTTP(w, r) }) }Use o pacote
contextpara criar um middleware que adicione um ID de requisição único ao contexto e um handler que o imprima na resposta.✓ Resposta:type ctxKey string const requestIDKey ctxKey = "requestID" func RequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := uuid.New().String() // ou use um gerador simples ctx := context.WithValue(r.Context(), requestIDKey, id) next.ServeHTTP(w, r.WithContext(ctx)) }) } // No handler: func handler(w http.ResponseWriter, r *http.Request) { id := r.Context().Value(requestIDKey).(string) w.Write([]byte("Request ID: " + id)) }Escreva uma função
Chainque aceite um handler e uma lista de middlewares, mas que aplique os middlewares na ordem inversa (o primeiro da lista é o mais interno). Explique a diferença.✓ Resposta:func ChainReverse(h http.Handler, mws ...Middleware) http.Handler { for _, mw := range mws { h = mw(h) } return h } // Nesse caso, o primeiro middleware da lista é o mais interno, ou seja, é executado por último. // A ordem de execução é invertida em relação à ordem da lista.Crie um middleware de compressão gzip que comprima a resposta se o cliente aceitar gzip. Use o pacote
compress/gzip.✓ Resposta:func GzipMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } w.Header().Set("Content-Encoding", "gzip") gz := gzip.NewWriter(w) defer gz.Close() gzw := &gzipResponseWriter{ResponseWriter: w, Writer: gz} next.ServeHTTP(gzw, r) }) } type gzipResponseWriter struct { http.ResponseWriter *gzip.Writer } func (g *gzipResponseWriter) Write(b []byte) (int, error) { return g.Writer.Write(b) }