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µs

Esse 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 chi ou gorilla/mux que oferecem suporte a middlewares integrados.

Referências

Exercícios

  1. Crie um middleware chamado RecoverMiddleware que 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)
        })
    }
  2. Implemente um middleware que adicione cabeçalhos de segurança (por exemplo, X-Content-Type-Options: nosniff e X-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)
        })
    }
  3. Use o pacote context para 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))
    }
  4. Escreva uma função Chain que 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.
  5. 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)
    }