Servidor HTTP (net/http)
Nesta aula, você aprenderá a criar servidores HTTP em Go usando o pacote net/http. Vamos explorar os conceitos de handlers, ServeMux e como iniciar o servidor, com exemplos práticos e exercícios para fixar o conteúdo.
O pacote net/http é uma das bibliotecas mais poderosas da linguagem Go, permitindo criar servidores HTTP robustos com poucas linhas de código. Nesta aula, vamos mergulhar nos fundamentos: como registrar rotas, como funciona o roteador padrão (ServeMux), como criar handlers e como iniciar o servidor de forma correta.
Entender esses conceitos é essencial para qualquer aplicação web em Go, desde uma API simples até um sistema complexo com middleware e rotas dinâmicas. Vamos explorar cada parte com exemplos práticos que você pode executar imediatamente.
http.HandleFunc
A função http.HandleFunc é a maneira mais simples de registrar uma função como handler para um padrão de rota. Ela recebe dois argumentos: o padrão (pattern) e a função handler. A função handler deve ter a assinatura func(w http.ResponseWriter, r *http.Request), onde w é usado para escrever a resposta e r contém os dados da requisição.
O padrão pode ser uma rota fixa como "/" ou "/hello", ou pode conter curingas (como "/api/" para capturar tudo que vem depois). O roteador padrão do Go (DefaultServeMux) faz a correspondência de padrões da forma mais longa primeiro.
Aqui está um exemplo simples de uso de http.HandleFunc:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Bem-vindo ao meu servidor!")
})
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Olá, visitante!")
})
http.ListenAndServe(":8080", nil) // nil usa o DefaultServeMux
}
Neste código, definimos duas rotas: a raiz e /hello. Quando o servidor recebe uma requisição para /hello, a função correspondente é chamada. O parâmetro r contém informações como método HTTP, cabeçalhos e corpo, que podem ser usados para lógica adicional.
ServeMux
ServeMux é o roteador HTTP padrão do Go. Ele implementa a interface http.Handler e é responsável por rotear requisições para os handlers registrados. Podemos criar um ServeMux personalizado em vez de usar o DefaultServeMux global, o que é uma boa prática para aplicações maiores, pois permite ter múltiplos roteadores isolados.
Um ServeMux pode ser criado com http.NewServeMux(). Depois, usamos o método HandleFunc para registrar rotas. A vantagem de um mux personalizado é que podemos passá-lo para http.ListenAndServe como segundo argumento, substituindo o mux padrão.
Exemplo:
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/about", aboutHandler)
http.ListenAndServe(":8080", mux)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Página inicial")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Sobre nós")
}
Note que também podemos usar mux.Handle para registrar um handler (um objeto que implementa http.Handler) em vez de uma função. Isso é útil quando temos tipos que implementam o método ServeHTTP.
Handlers
Um handler em Go é qualquer tipo que implemente a interface http.Handler, que exige o método ServeHTTP(w http.ResponseWriter, r *http.Request). Essa interface é central no pacote net/http; tudo que recebe uma requisição e produz uma resposta é um handler.
Além de funções simples, podemos criar structs que implementam ServeHTTP para encapsular lógica mais complexa. Isso é comum em aplicações reais, onde cada rota pode ter seu próprio tipo de handler com dependências (como banco de dados ou serviços).
Exemplo de um handler como struct:
package main
import (
"fmt"
"net/http"
)
type apiHandler struct {
// campos, como um serviço de banco
}
func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "API handler")
}
func main() {
mux := http.NewServeMux()
mux.Handle("/api", apiHandler{})
http.ListenAndServe(":8080", mux)
}
Também podemos usar funções como handlers através da conversão http.HandlerFunc, que é um tipo de função que implementa ServeHTTP. Isso é o que acontece por baixo dos panos quando usamos HandleFunc.
Outra técnica importante é o encadeamento de handlers (middleware), onde um handler envolve outro para adicionar funcionalidades como logging ou autenticação. Isso será explorado em aulas futuras.
Iniciando o servidor
Para iniciar um servidor HTTP, usamos a função http.ListenAndServe, que recebe um endereço (como ":8080") e um handler (normalmente um ServeMux). Se passarmos nil, o Go usa o DefaultServeMux. Essa função bloqueia e escuta na porta especificada até ser interrompida.
É importante tratar erros ao iniciar o servidor, pois a porta pode estar em uso ou o endereço pode ser inválido. Podemos usar log.Fatal para exibir o erro e encerrar o programa, ou tratar o erro de forma mais elaborada.
Exemplo com tratamento de erro:
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Olá, mundo!"))
})
log.Println("Servidor rodando em http://localhost:8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
Existe também a função http.Server para configurações mais avançadas, como timeouts e TLS. Isso permite maior controle sobre o servidor, mas para a maioria dos casos, ListenAndServe é suficiente.
Lembre-se de que o servidor roda de forma síncrona; para executar outras tarefas enquanto o servidor está ativo, você pode iniciá-lo em uma goroutine.
Boas práticas e observações finais
Ao desenvolver servidores HTTP em Go, algumas boas práticas incluem: usar um ServeMux personalizado para isolar rotas, evitar o uso do DefaultServeMux em produção, sempre tratar erros de forma adequada, e usar nomes de handlers descritivos. Além disso, é recomendado definir timeouts no servidor para evitar ataques de lentidão.
Outra dica: use o padrão de rota com barra final ("/api/") para capturar sub-rotas, mas lembre-se de que o Go faz redirecionamento automático de "/api" para "/api/" se o padrão terminar com barra. Isso pode causar confusão, então teste bem suas rotas.
Por fim, explore a documentação oficial e os exemplos para aprofundar seu conhecimento.
Referências
- Documentação oficial do pacote net/http
- Tutorial: Escrevendo aplicações web em Go
- Servindo sites estáticos com Go (Alex Edwards)
- Concorrência no net/http
- Documentação do ServeMux
- Wiki do Go sobre roteadores HTTP
Exercícios
- Crie um servidor HTTP que responda "Olá, mundo!" na rota
/".✓ Resposta:package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Olá, mundo!") }) http.ListenAndServe(":8080", nil) } - Registre duas rotas:
/e/sobre, cada uma retornando uma mensagem diferente. Use umServeMuxpersonalizado.✓ Resposta:package main import ( "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Página inicial") }) mux.HandleFunc("/sobre", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Sobre nós") }) http.ListenAndServe(":8080", mux) } - Crie um handler do tipo struct que implemente
http.Handlere registre-o na rota/api.✓ Resposta:package main import ( "fmt" "net/http" ) type apiHandler struct{} func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "API handler") } func main() { mux := http.NewServeMux() mux.Handle("/api", apiHandler{}) http.ListenAndServe(":8080", mux) } - Inicie um servidor na porta 9090 e trate possíveis erros usando
log.Fatal.✓ Resposta:package main import ( "log" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Servidor na porta 9090")) }) if err := http.ListenAndServe(":9090", mux); err != nil { log.Fatal(err) } } - Use a função
http.Serverpara criar um servidor com configuração de timeout de leitura de 5 segundos e timeout de escrita de 10 segundos.✓ Resposta:package main import ( "net/http" "time" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Servidor com timeouts")) }) server := &http.Server{ Addr: ":8080", Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } if err := server.ListenAndServe(); err != nil { panic(err) } }