Servidor HTTP Nativo Moderno em Go (1.22+)
Como criar APIs REST completas só com a biblioteca padrão, usando o roteador http.ServeMux do Go 1.22+ com verbos e parâmetros.
Publicada em · 6 min de leitura
Servidor HTTP Nativo Moderno em Go (1.22+)
Por mais de uma década, construir uma API RESTful idiomática em Go com a biblioteca padrão era um exercício frustrante. O antigo http.ServeMux só suportava correspondência simples de prefixos de URL: não distinguia verbos HTTP (GET vs POST), não extraía parâmetros de rota (/users/{id}) e exigia que quase todo projeto adotasse bibliotecas externas como Gorilla Mux, Chi, Echo ou Gin.
A partir do Go 1.22, a equipe do Go reformulou completamente o http.ServeMux, introduzindo roteamento declarativo por método, captura nativa de parâmetros com r.PathValue() e wildcards. Com essa revolução, somada ao suporte a sistemas de arquivos embutidos (embed.FS), a biblioteca padrão do Go tornou-se mais do que suficiente para a esmagadora maioria das APIs corporativas, eliminando dependências externas desnecessárias.
1. O Novo http.ServeMux: Recursos e Sintaxe
O http.ServeMux moderno permite registrar padrões que combinam o verbo HTTP, segmentos exatos de caminho, variáveis de rota e curingas residuais.
Padrões de Roteamento Suportados
| Padrão | Sintaxe de Registro | Como Obter o Valor |
|---|---|---|
| Método + Caminho Exato | "GET /healthz" | — |
| Método + Path Parameter | "GET /users/{id}" | id := r.PathValue("id") |
| Múltiplas Variáveis | "GET /orgs/{org}/repos/{repo}" | r.PathValue("org"), r.PathValue("repo") |
| Wildcard Residual | "GET /files/{filepath...}" | sub := r.PathValue("filepath") |
| Hostname Específico | "api.empresa.com/v1/" | Match condicionado ao header Host |
Tratamento Automático de 405 Method Not Allowed
No Go 1.22+, se uma rota foi registrada para POST /items, e o cliente efetuar uma requisição GET /items, o ServeMux automaticamente responderá com HTTP 405 Method Not Allowed e preencherá o cabeçalho Allow: POST, respeitando a especificação RFC 9110 sem uma única linha de código extra!
2. Implementando uma API REST Nativa
Veja como estruturar rotas e handlers de forma limpa, fortemente tipada e com tratamento de erros sem nenhuma dependência externa:
package main
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"strconv"
)
type Product struct {
ID int64 `json:"id,string"`
Title string `json:"title"`
Price float64 `json:"price"`
}
type ProductHandler struct {
// Injetar dependências de serviço/repositório aqui
}
func (h *ProductHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/v1/products", h.List)
mux.HandleFunc("GET /api/v1/products/{id}", h.GetByID)
mux.HandleFunc("POST /api/v1/products", h.Create)
mux.HandleFunc("DELETE /api/v1/products/{id}", h.Delete)
}
func (h *ProductHandler) GetByID(w http.ResponseWriter, r *http.Request) {
// Extração nativa do parâmetro {id}
idStr := r.PathValue("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 {
http.Error(w, `{"error":"id de produto inválido"}`, http.StatusBadRequest)
return
}
product := Product{ID: id, Title: "Teclado Mecânico Custom", Price: 550.00}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(product)
}
func (h *ProductHandler) Create(w http.ResponseWriter, r *http.Request) {
var input Product
// Decodificação direta sem buffer intermediário
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, `{"error":"payload JSON inválido"}`, http.StatusBadRequest)
return
}
input.ID = 100201 // Simula inserção em banco de dados
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(input)
}
func (h *ProductHandler) List(w http.ResponseWriter, r *http.Request) {
// Query params continuam acessíveis via r.URL.Query()
limit := r.URL.Query().Get("limit")
_ = limit
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode([]Product{})
}
func (h *ProductHandler) Delete(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_ = id
w.WriteHeader(http.StatusNoContent)
}
3. Servindo Arquivos Estáticos e SPAs com //go:embed
Uma das maiores forças do Go em infraestrutura e DevOps é a capacidade de compilar toda a aplicação — incluindo interfaces frontend (HTML, JavaScript, CSS, imagens e Swagger UI) — em um único binário executável autocontido, eliminando dependências de arquivos externos no filesystem do contêiner Docker.
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
// Diretiva que embute o diretório dist/ do React/Vue no binário Go durante o build
//
//go:embed all:dist
var frontendDist embed.FS
func RegisterStaticRoutes(mux *http.ServeMux) {
// Cria um sub-sistema de arquivos removendo a pasta raiz "dist"
distFS, err := fs.Sub(frontendDist, "dist")
if err != nil {
log.Fatalf("falha ao carregar assets embutidos: %v", err)
}
// FileServerFS é o método nativo moderno para servir instâncias de fs.FS
fileServer := http.FileServerFS(distFS)
// Captura todas as rotas estáticas
mux.Handle("GET /static/{filepath...}", http.StripPrefix("/static/", fileServer))
}
4. Configuração Blindada do http.Server para Produção
Assim como não se deve usar o http.DefaultClient, nunca suba um servidor com o método simplista http.ListenAndServe(":8080", mux). Essa função utiliza internamente uma instância padrão de http.Server com todos os timeouts zerados, deixando o servidor suscetível ao clássico ataque de exaustão de conexões Slowloris.
package server
import (
"log/slog"
"net/http"
"time"
)
// NewSecureServer instancia o servidor HTTP com limites estritos de segurança
func NewSecureServer(addr string, handler http.Handler) *http.Server {
return &http.Server{
Addr: addr,
Handler: handler,
// DEFESA VITAL CONTRA SLOWLORIS:
// Limita o tempo que o cliente tem para enviar os headers da requisição.
ReadHeaderTimeout: 2 * time.Second,
// Tempo máximo para leitura completa da requisição (headers + body)
ReadTimeout: 5 * time.Second,
// Tempo máximo do início da escrita da resposta até o fim da transmissão
WriteTimeout: 10 * time.Second,
// Tempo que uma conexão TCP Keep-Alive pode permanecer ociosa antes de fechar
IdleTimeout: 120 * time.Second,
// Limita o tamanho dos cabeçalhos a 1 Megabyte (evita estouro de RAM com cookies gigantes)
MaxHeaderBytes: 1 << 20,
// Integração com log estruturado nativo
ErrorLog: slog.NewLogLogger(slog.Default().Handler(), slog.LevelError),
}
}
5. Middlewares Nativos sem Frameworks
Em Go, qualquer função que respeite a assinatura func(http.Handler) http.Handler atua perfeitamente como middleware. Podemos compor middlewares de recuperação de pânico, logging e cabeçalhos de segurança de forma extremamente limpa.
package middleware
import (
"log/slog"
"net/http"
"time"
)
// Middleware é o contrato padrão de interceptação HTTP em Go
type Middleware func(http.Handler) http.Handler
// CreateStack encadeia múltiplos middlewares em ordem de execução
func CreateStack(xs ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(xs) - 1; i >= 0; i-- {
next = xs[i](next)
}
return next
}
}
// Recovery captura pânicos não tratados e retorna 500 sem derrubar o processo
func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
slog.Error("Pânico não tratado capturado", slog.Any("panic", rec))
http.Error(w, `{"error":"erro interno do servidor"}`, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// RequestLogger gera logs estruturados com tempo de execução
func RequestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("HTTP Request Concluída",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.Duration("latency", time.Since(start)),
)
})
}
Uso no main.go:
mux := http.NewServeMux()
productHandler.RegisterRoutes(mux)
stack := middleware.CreateStack(
middleware.Recovery,
middleware.RequestLogger,
)
server := server.NewSecureServer(":8080", stack(mux))
6. Comparativo: Stdlib Moderna vs Roteadores Externos
| Requisito | http.ServeMux (Go 1.22+) | Chi / Gorilla | Gin / Fiber |
|---|---|---|---|
| Dependências Externas | Zero | Baixa | Alta |
| Roteamento por Verbo HTTP | Sim (GET /users) | Sim | Sim |
| Extração de Path Params | Sim (r.PathValue) | Sim | Sim |
| Wildcards Residuais | Sim ({path...}) | Sim | Sim |
| Regex em Rotas | Não (requer validação no handler) | Sim | Sim |
| Alocação de Memória | Mínima (Radix Tree otimizada) | Mínima | Otimizada via Radix |
| Longevidade e Manutenção | Garantia de compatibilidade Go 1 | Sujeito a abandono | Sujeito a breaking changes |
Veredito de Arquitetura:
Para novos projetos corporativos em Go 1.22+, comece sempre com a biblioteca padrão. Adote roteadores como Chi apenas se sua aplicação depender pesadamente de rotas com expressões regulares complexas ou agrupamento hierárquico avançado de sub-roteadores com middlewares específicos por grupo.
7. Checklist de Produção para Servidores HTTP
- Abandonar frameworks pesados e adotar
http.ServeMuxnativo do Go 1.22+. - Configurar explicitamente
ReadHeaderTimeoutpara anular ataques de Slowloris. - Configurar
ReadTimeout,WriteTimeouteIdleTimeout. - Definir
MaxHeaderBytespara proteger contra estouro de memória por cabeçalhos abusivos. - Utilizar
r.PathValue("chave")para extrair variáveis limpas de URL. - Implementar middleware de
Recoverypara capturar pânicos inesperados sem abortar a instância. - Integrar com Graceful Shutdown e Signal Handling para aguardar requisições em voo no encerramento de pods Kubernetes.
Links Relacionados
- Manipulação de Arquivos e IO: Utilizando
embed.FSe servindo arquivos de sistema. - Trabalhando com JSON: Parsing e decodificação estrita de requisições.
- Middlewares em Go: Construção aprofundada de interceptadores e autenticação.
- Graceful Shutdown e Signal Handling: Encerramento seguro de servidores
http.Server.