Roteamento e a ServeMux moderna
Nesta aula, exploramos o roteamento moderno da net/http em Go 1.22+, incluindo padrões de rota com métodos HTTP, path parameters e a comparação com frameworks populares. Aprendemos a usar o ServeMux aprimorado para criar rotas limpas e eficientes sem dependências externas.
O pacote net/http do Go sempre forneceu um roteador básico, o http.ServeMux, mas até a versão 1.21 ele era limitado: não suportava métodos HTTP específicos, nem parâmetros de caminho de forma nativa. Desenvolvedores frequentemente recorriam a bibliotecas de terceiros como gorilla/mux ou chi para obter esses recursos. A partir do Go 1.22, o ServeMux foi significativamente aprimorado, introduzindo um novo padrão de roteamento que permite especificar métodos, capturar parâmetros de caminho e até mesmo usar curingas. Isso torna o roteamento nativo muito mais poderoso e reduz a necessidade de dependências externas para muitos casos de uso.
Nesta aula, vamos mergulhar nas novas capacidades do http.ServeMux, entender como escrever rotas expressivas e comparar com abordagens baseadas em frameworks. Ao final, você terá uma compreensão sólida para escolher entre o roteamento nativo e bibliotecas externas em seus projetos.
Padrões de rota (Go 1.22+)
Antes do Go 1.22, o ServeMux usava padrões baseados em prefixo, sem suporte a métodos ou parâmetros. Agora, o padrão de rota pode incluir o método HTTP e segmentos de caminho com curingas. A sintaxe básica é "MÉTODO /caminho/segmento", onde o método é opcional (se omitido, a rota responde a qualquer método). Os padrões podem conter curingas como {nome} para capturar um segmento, ou {nome...} para capturar o restante do caminho.
Vamos ver um exemplo simples de registro de rotas com métodos e curingas:
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Rota exata para GET /hello
mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Olá, mundo!")
})
// Rota com curinga {name} para GET /users/{name}
mux.HandleFunc("GET /users/{name}", func(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
fmt.Fprintf(w, "Usuário: %s\n", name)
})
// Rota curinga para /files/{path...} captura o restante
mux.HandleFunc("GET /files/{path...}", func(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path")
fmt.Fprintf(w, "Caminho: %s\n", path)
})
http.ListenAndServe(":8080", mux)
}
O curinga {name} captura exatamente um segmento (não pode conter barra), enquanto {path...} captura o restante do caminho, incluindo barras. Isso permite criar rotas flexíveis sem precisar de bibliotecas externas.
Métodos
Uma das maiores adições é a capacidade de associar um método HTTP a um padrão. Antes, era necessário verificar manualmente r.Method dentro do handler. Agora, o roteador cuida disso, retornando 405 Method Not Allowed se o método não corresponder, e 404 se o padrão não existir. Isso torna o código mais limpo e seguro.
É importante notar que, se você especificar um método, a rota só responderá àquele método. Para rotas que aceitam qualquer método, basta omitir o método no padrão. Exemplo:
mux.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// tratar GET
case http.MethodPost:
// tratar POST
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
})
Com o novo padrão, você pode registrar handlers separados para cada método, o que melhora a organização e elimina a necessidade de switch:
mux.HandleFunc("GET /api/data", getData)
mux.HandleFunc("POST /api/data", postData)
mux.HandleFunc("PUT /api/data", putData)
Se um método não for suportado, o ServeMux retorna automaticamente o status 405. Isso é um grande ganho de produtividade e consistência.
Path parameters
Os path parameters são acessados através do método r.PathValue("nome") no handler. Isso substitui a necessidade de analisar a URL manualmente com strings.Split ou expressões regulares. Os parâmetros são extraídos de forma segura e tipada (sempre string), e você pode convertê-los conforme necessário.
Vamos ver um exemplo completo com múltiplos parâmetros e validação:
mux.HandleFunc("GET /products/{category}/{id}", func(w http.ResponseWriter, r *http.Request) {
category := r.PathValue("category")
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "ID inválido", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Categoria: %s, ID: %d\n", category, id)
})
Além disso, o ServeMux garante que os parâmetros estejam preenchidos na rota correspondente, então não é necessário verificar se a chave existe. Isso reduz erros comuns. Para rotas curinga como {path...}, o valor pode ser vazio se não houver segmentos restantes, mas o padrão deve ser usado com cuidado para não capturar rotas indesejadas.
Comparação com frameworks
Frameworks como gin, echo e chi oferecem recursos adicionais, como middleware, validação, agrupamento de rotas, e integração com contextos enriquecidos. No entanto, o ServeMux nativo agora cobre a maioria das necessidades básicas de roteamento com menos dependências e menor overhead.
Vamos comparar um exemplo de rota com parâmetros em chi e no ServeMux nativo:
// Usando chi
r := chi.NewRouter()
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// ...
})
// Usando ServeMux nativo (Go 1.22+)
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
})
A sintaxe é quase idêntica, mas o nativo não requer importações adicionais. Para projetos simples a médios, o ServeMux é suficiente. No entanto, se você precisa de middleware reutilizável, roteamento aninhado complexo ou recursos como validação automática, frameworks como chi ainda podem ser vantajosos. A escolha depende da complexidade do projeto e da preferência da equipe.
Boas práticas
Ao usar o novo ServeMux, é importante:
- Definir métodos explicitamente para evitar rotas acidentais.
- Usar parâmetros nomeados em vez de expressões regulares para legibilidade.
- Evitar curingas excessivos, pois podem capturar rotas inesperadas.
- Manter os handlers pequenos e delegar a lógica para funções separadas.
- Utilizar
http.Servercom timeouts para produção.
Referências
- Go Blog: Routing Enhancements
- Documentação oficial net/http ServeMux
- Notas da versão Go 1.22
- Chi - Roteador compatível com net/http
- Gin - Framework web
- Echo - Framework web
Exercícios
-
Crie um servidor HTTP com
ServeMuxque tenha uma rotaGET /pingrespondendo "pong".✓ Resposta:package main import ( "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /ping", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "pong") }) http.ListenAndServe(":8080", mux) } -
Adicione uma rota
POST /echoque leia o corpo da requisição e o devolva como resposta.✓ Resposta:mux.HandleFunc("POST /echo", func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Erro ao ler corpo", http.StatusInternalServerError) return } w.Write(body) }) -
Implemente uma rota
GET /users/{id}que retorna o ID recebido como parâmetro.✓ Resposta:mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "ID: %s\n", id) }) -
Crie uma rota
GET /files/{path...}que retorna o caminho completo capturado.✓ Resposta:mux.HandleFunc("GET /files/{path...}", func(w http.ResponseWriter, r *http.Request) { path := r.PathValue("path") fmt.Fprintf(w, "Caminho: %s\n", path) }) -
Escreva um servidor que responda
200 OKpara qualquer método em/health, mas retorne405para métodos não permitidos em outras rotas.✓ Resposta:mux := http.NewServeMux() mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) mux.HandleFunc("/other", func(w http.ResponseWriter, r *http.Request) { // Esta rota só aceita GET, mas se você tentar POST, o ServeMux retorna 405 automaticamente. fmt.Fprintln(w, "OK") })Nota: Para que o
ServeMuxretorne 405 automaticamente, você deve especificar o método no padrão. No exemplo acima,/healthaceita qualquer método, então não retornará 405. Para testar 405, defina um método específico, ex.:mux.HandleFunc("GET /other", ...).
Observações finais
O aprimoramento do ServeMux no Go 1.22 é um passo significativo para tornar a biblioteca padrão mais competitiva com frameworks. Para projetos que não exigem funcionalidades avançadas, o uso do ServeMux nativo reduz dependências e simplifica o deploy. No entanto, frameworks ainda oferecem conveniências como middleware reutilizável e validação, então avalie as necessidades do seu projeto. Com o conhecimento desta aula, você está apto a escolher a melhor abordagem.