Servidor HTTP (net/http)
Esta aula apresenta o pacote net/http da linguagem Go, cobrindo os conceitos fundamentais para criar servidores HTTP: a função http.HandleFunc, o roteador ServeMux, o tipo Handler e como iniciar o servidor com ListenAndServe. Inclui exemplos práticos, boas práticas e exercícios com respostas.
Nesta aula, vamos explorar o pacote net/http da linguagem Go, que fornece todas as ferramentas necessárias para criar servidores HTTP. Você aprenderá a registrar rotas, manipular requisições e respostas, e iniciar um servidor web de forma simples e eficiente. Ao final, você terá uma base sólida para construir aplicações web em Go.
O Go é conhecido por sua simplicidade e desempenho, e o pacote net/http não é exceção. Ele oferece uma API limpa e idiomática, permitindo que você monte servidores HTTP com poucas linhas de código. Vamos começar com os conceitos básicos e avançar para exemplos mais complexos.
http.HandleFunc
A função http.HandleFunc é uma das maneiras mais diretas de registrar uma rota em um servidor HTTP. Ela associa um padrão de URL a uma função handler, que será chamada sempre que uma requisição corresponder a esse padrão.
A assinatura da função é: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)). O primeiro argumento é o padrão de URL (ex: "/", "/hello"), e o segundo é uma função que recebe um http.ResponseWriter e um ponteiro para http.Request. Essa função é responsável por processar a requisição e escrever a resposta.
Exemplo básico:
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Olá, mundo!")
}
func main() {
http.HandleFunc("/hello", helloHandler)
http.ListenAndServe(":8080", nil)
}Neste exemplo, qualquer requisição para /hello chamará a função helloHandler, que escreve "Olá, mundo!" na resposta. Note que http.HandleFunc registra a rota no DefaultServeMux (o mux padrão), sobre o qual falaremos a seguir.
ServeMux
O ServeMux é um roteador de requisições HTTP. Ele implementa a interface http.Handler e é responsável por encaminhar cada requisição para o handler registrado para o padrão de URL correspondente. O pacote net/http fornece um DefaultServeMux que é usado por padrão, mas você pode criar seus próprios muxes para ter mais controle.
Um ServeMux pode ser criado com http.NewServeMux(). Em seguida, você registra rotas usando o método HandleFunc ou Handle. A vantagem de usar um mux personalizado é que você pode passá-lo explicitamente para http.ListenAndServe, em vez de usar o mux global.
Exemplo com mux personalizado:
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Página inicial")
})
mux.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Sobre nós")
})
http.ListenAndServe(":8080", mux)
}O ServeMux também suporta padrões mais complexos, como caminhos com curingas (a partir do Go 1.22) e métodos HTTP específicos. Por exemplo, você pode registrar "GET /api/users" para responder apenas a requisições GET. Para mais detalhes, consulte a documentação oficial.
Handlers
Um Handler é uma interface que define o método ServeHTTP(ResponseWriter, *Request). Qualquer tipo que implemente esse método pode ser usado como um handler. O ServeMux é um exemplo de handler, pois implementa ServeHTTP e delega para o handler correto.
Você pode definir seus próprios handlers como structs ou funções. Além disso, o pacote net/http fornece funções utilitárias como http.HandlerFunc, que adapta uma função comum para a interface Handler. Isso é conveniente para criar handlers inline.
Exemplo de handler como struct:
package main
import (
"fmt"
"net/http"
)
type messageHandler struct {
message string
}
func (m messageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, m.message)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/hello", messageHandler{"Olá, mundo!"})
mux.Handle("/goodbye", messageHandler{"Adeus!"})
http.ListenAndServe(":8080", mux)
}Perceba que usamos mux.Handle em vez de HandleFunc, pois estamos passando um objeto que implementa a interface Handler. Essa abordagem é útil quando você precisa manter estado dentro do handler.
Iniciando o servidor
Para iniciar um servidor HTTP, utilizamos a função http.ListenAndServe. Ela recebe o endereço (host e porta) e um handler (ou nil para usar o DefaultServeMux). A função bloqueia e atende requisições até que o processo seja encerrado.
Exemplo simples:
http.ListenAndServe(":8080", nil)Você também pode usar http.Server para ter mais controle sobre o servidor, como configurar timeouts e TLS. O tipo http.Server permite configurar propriedades como Addr, Handler, ReadTimeout, WriteTimeout, entre outros.
Exemplo com http.Server:
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe())Neste exemplo, criamos um servidor com timeouts de leitura e escrita de 10 segundos. Isso é importante para evitar que conexões lentas prendam recursos indefinidamente. Sempre use log.Fatal para capturar erros ao iniciar o servidor.
Boas Práticas
- Use um
ServeMuxpersonalizado em vez do global para evitar conflitos e facilitar testes. - Defina timeouts no servidor para proteger contra ataques de lentidão.
- Estruture seus handlers em arquivos separados, agrupando por funcionalidade.
- Utilize
http.Errorpara enviar respostas de erro adequadas. - Evite usar o padrão
"/"para capturar todas as rotas, a menos que seja necessário.
Referências
- Documentação oficial do pacote net/http
- Tutorial: Escrevendo aplicações web em Go
- Usando módulos Go
- Recapitulação do tratamento de requisições no Go
- Exemplo de servidor HTTP no Go by Example
Exercícios
Crie um servidor HTTP que responda "Olá, Go!" na rota
/e "Bem-vindo!" na rota/welcome. Use oDefaultServeMux.✓ Resposta:package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Olá, Go!") }) http.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Bem-vindo!") }) http.ListenAndServe(":8080", nil) }Refatore o exercício anterior para usar um
ServeMuxpersonalizado.✓ Resposta:package main import ( "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Olá, Go!") }) mux.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Bem-vindo!") }) http.ListenAndServe(":8080", mux) }Crie um handler do tipo struct que tenha um campo
mensageme implementeServeHTTPpara escrever essa mensagem. Registre-o em duas rotas diferentes.✓ Resposta:package main import ( "fmt" "net/http" ) type messageHandler struct { message string } func (m messageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, m.message) } func main() { mux := http.NewServeMux() mux.Handle("/msg1", messageHandler{"Primeira mensagem"}) mux.Handle("/msg2", messageHandler{"Segunda mensagem"}) http.ListenAndServe(":8080", mux) }Inicie um servidor usando
http.Servercom timeouts de leitura e escrita de 5 segundos e um mux personalizado.✓ Resposta:package main import ( "fmt" "net/http" "time" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Servidor com timeouts") }) srv := &http.Server{ Addr: ":8080", Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, } log.Fatal(srv.ListenAndServe()) }Explique a diferença entre
http.HandleFuncehttp.Handle. Em que situações você usaria cada um?✓ Resposta:http.HandleFuncé uma conveniência que adapta uma função comum para a interfaceHandler, enquantohttp.Handleespera um valor que implementeHandlerdiretamente. UseHandleFuncquando você tem uma função simples e não precisa de estado; useHandlequando você tem um tipo personalizado (struct) que implementaServeHTTPe precisa de estado ou lógica complexa.