Middleware em HTTP
Nesta aula, você aprenderá a criar e encadear middlewares em Go, desde o padrão básico até aplicações práticas como logging e autenticação, com exemplos completos e exercícios para fixar o conteúdo.
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, entre outros. Em Go, o padrão mais comum é usar a assinatura func(http.Handler) http.Handler, que recebe um handler e retorna outro, permitindo o encadeamento de forma elegante.
Nesta aula, vamos explorar o conceito de middleware, como encadeá-los, e implementar exemplos práticos de logging e autenticação. Você verá como essa técnica é essencial para construir aplicações web robustas e manuteníveis.
Padrão de middleware
O padrão de middleware em Go se baseia na interface http.Handler, que possui o método ServeHTTP(ResponseWriter, *Request). Um middleware é uma função que recebe um http.Handler e retorna outro http.Handler, permitindo executar código antes e depois do handler original.
Essa abordagem é funcional e composável: você pode criar funções que envolvem outras, formando uma cadeia de processamento. O middleware pode modificar a requisição, capturar a resposta, ou simplesmente executar ações colaterais como registrar logs.
package main
import (
"fmt"
"net/http"
)
// Middleware é uma função que recebe um http.Handler e retorna um novo http.Handler.
type Middleware func(http.Handler) http.Handler
// exemplo de middleware simples que imprime uma mensagem antes de chamar o próximo handler.
func meuMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Antes do handler")
proximo.ServeHTTP(w, r)
fmt.Println("Depois do handler")
})
}
func handlerPrincipal(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Olá, mundo!")
}
func main() {
// Aplicando o middleware ao handler principal.
handler := meuMiddleware(http.HandlerFunc(handlerPrincipal))
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}Nesse exemplo, o middleware imprime mensagens antes e depois da execução do handler. Note que usamos http.HandlerFunc para converter nossa função no tipo http.Handler.
Encadeamento
Para encadear vários middlewares, podemos simplesmente aplicar um sobre o outro. Uma forma comum é criar uma função que recebe uma lista de middlewares e aplica em ordem reversa, para que o primeiro da lista seja o mais externo.
Vamos criar uma função encadear que recebe um handler final e uma lista de middlewares, e retorna o handler já envolvido por todos eles.
package main
import (
"fmt"
"net/http"
)
func encadear(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
// Aplicamos em ordem reversa para que o primeiro middleware seja o mais externo.
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func logMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("LOG: %s %s\n", r.Method, r.URL.Path)
proximo.ServeHTTP(w, r)
})
}
func authMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "segredo" {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
proximo.ServeHTTP(w, r)
})
}
func handlerPrincipal(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Bem-vindo!")
}
func main() {
// Encadeando log e auth.
handler := encadear(http.HandlerFunc(handlerPrincipal), logMiddleware, authMiddleware)
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}Assim, a ordem de execução é: primeiro o log, depois a autenticação, e por último o handler principal. Se a autenticação falhar, a requisição é interrompida e o handler principal não é chamado.
Logging e auth
Vamos detalhar dois middlewares muito comuns: logging e autenticação. O logging registra informações sobre cada requisição, como método, caminho, duração, status da resposta, etc. Já a autenticação valida credenciais (como token) antes de liberar o acesso.
No exemplo a seguir, implementamos um middleware de logging que mede o tempo de processamento e um middleware de autenticação simples baseado em token. Vamos também mostrar como capturar o status da resposta usando um ResponseWriter personalizado.
package main
import (
"fmt"
"net/http"
"time"
)
// responseWriter captura o status code.
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
func logMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
proximo.ServeHTTP(rw, r)
fmt.Printf("%s %s %d %s\n", r.Method, r.URL.Path, rw.status, time.Since(inicio))
})
}
func authMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "segredo" {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
proximo.ServeHTTP(w, r)
})
}
func handlerPrincipal(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Conteúdo protegido")
}
func main() {
handler := logMiddleware(authMiddleware(http.HandlerFunc(handlerPrincipal)))
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}No middleware de logging, criamos um responseWriter que captura o status code. Isso é necessário porque o http.ResponseWriter original não expõe o status depois que é enviado.
Exemplos
Além de logging e autenticação, existem muitos outros usos para middlewares. Vamos ver um exemplo de middleware de recuperação de pânico (panic recovery) que evita que o servidor caia se um handler lançar uma exceção.
package main
import (
"fmt"
"net/http"
)
func recoveryMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("Recuperado de panic: %v\n", err)
http.Error(w, "Erro interno", http.StatusInternalServerError)
}
}()
proximo.ServeHTTP(w, r)
})
}
func handlerQuePanica(w http.ResponseWriter, r *http.Request) {
panic("algo deu errado")
}
func main() {
handler := recoveryMiddleware(http.HandlerFunc(handlerQuePanica))
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}Esse middleware garante que, mesmo se um handler entrar em pânico, o servidor responda com um erro 500 e continue funcionando. É uma prática recomendada para aplicações em produção.
Boas práticas
Ao criar middlewares, mantenha-os pequenos e focados em uma única responsabilidade. Use nomes descritivos e documente o comportamento. Prefira compor middlewares em vez de criar um gigante que faz tudo. Além disso, sempre considere a ordem: middlewares de segurança (auth) devem vir antes de middlewares que expõem informações (logging), para não vazar dados de requisições não autorizadas.
Referências
- Documentação oficial do pacote net/http
- Blog oficial de Go
- Effective Go
- Making and Using Middleware in Go
- Writing Middleware in Go
Exercícios
- Crie um middleware que adicione um cabeçalho
X-Middlewarecom valortruea todas as respostas. - Escreva um middleware que limite o tamanho do corpo da requisição a 1MB. Use
http.MaxBytesReader. - Implemente um middleware que adicione um atraso de 1 segundo em todas as requisições (simulando latência).
- Crie um middleware que verifique se a requisição é do tipo GET e, caso contrário, retorne 405 Method Not Allowed.
- Escreva um middleware que faça o log da requisição e também adicione um cabeçalho de resposta com o tempo de processamento (em milissegundos).
func headerMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Middleware", "true")
proximo.ServeHTTP(w, r)
})
}func limitBodyMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1MB
proximo.ServeHTTP(w, r)
})
}func delayMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second)
proximo.ServeHTTP(w, r)
})
}func getOnlyMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
return
}
proximo.ServeHTTP(w, r)
})
}func logAndTimingMiddleware(proximo http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
proximo.ServeHTTP(w, r)
duracao := time.Since(inicio)
w.Header().Set("X-Tempo-Processamento", fmt.Sprintf("%dms", duracao.Milliseconds()))
fmt.Printf("%s %s %v\n", r.Method, r.URL.Path, duracao)
})
}