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

Exercícios

  1. Crie um middleware que adicione um cabeçalho X-Middleware com valor true a todas as respostas.
  2. ✓ Resposta:
    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)
        })
    }
  3. Escreva um middleware que limite o tamanho do corpo da requisição a 1MB. Use http.MaxBytesReader.
  4. ✓ Resposta:
    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)
        })
    }
  5. Implemente um middleware que adicione um atraso de 1 segundo em todas as requisições (simulando latência).
  6. ✓ Resposta:
    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)
        })
    }
  7. Crie um middleware que verifique se a requisição é do tipo GET e, caso contrário, retorne 405 Method Not Allowed.
  8. ✓ Resposta:
    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)
        })
    }
  9. 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).
  10. ✓ Resposta:
    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)
        })
    }