HTTP server com net/http
1. Introdução ao pacote net/http
O pacote net/http é a espinha dorsal da construção de servidores HTTP em Go. Diferentemente de outras linguagens que exigem frameworks externos para tarefas básicas, Go oferece uma biblioteca padrão robusta e completa para criar servidores HTTP de produção.
A estrutura mais simples de um servidor HTTP utiliza http.ListenAndServe, que aceita um endereço (como ":8080") e um handler. O handler é qualquer valor que implemente a interface http.Handler, que exige apenas um método: ServeHTTP(ResponseWriter, *Request).
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Olá, mundo!"))
})
http.ListenAndServe(":8080", nil)
}
A diferença entre Handler e HandlerFunc é sutil mas importante. http.Handler é uma interface, enquanto http.HandlerFunc é um tipo de função que implementa essa interface automaticamente. Isso permite que funções comuns sejam usadas como handlers sem necessidade de structs especiais.
2. Trabalhando com multiplexadores (mux)
O multiplexador (ou roteador) decide qual handler atenderá cada requisição. O http.DefaultServeMux é o mux global usado quando passamos nil para ListenAndServe. Para mais controle, usamos http.NewServeMux().
mux := http.NewServeMux()
mux.HandleFunc("/api/hello", helloHandler)
mux.Handle("/api/admin", adminMiddleware(adminHandler))
http.ListenAndServe(":8080", mux)
A diferença entre Handle e HandleFunc é que Handle espera um http.Handler (interface), enquanto HandleFunc aceita uma função com assinatura func(ResponseWriter, *Request).
Com Go 1.22+, o pattern matching foi modernizado para suportar métodos HTTP e variáveis de path:
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUserByID)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("DELETE /users/{id}", deleteUser)
3. Handlers personalizados e middlewares
Handlers como structs são úteis quando precisamos de estado ou dependências injetadas:
type UserHandler struct {
db *sql.DB
}
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// lógica que usa h.db
}
Middlewares são funções que recebem e retornam http.Handler, permitindo empilhar comportamentos:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func compressionMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
return
}
next.ServeHTTP(w, r)
})
}
4. Manipulação de requisições e respostas
A leitura de parâmetros varia conforme o tipo:
// Query parameters
name := r.URL.Query().Get("name")
// Path variables (Go 1.22+)
userID := r.PathValue("id")
// Form data
email := r.FormValue("email")
Cabeçalhos são acessados de forma similar em requisições e respostas:
// Lendo cabeçalhos da requisição
authToken := r.Header.Get("Authorization")
// Escrevendo cabeçalhos na resposta
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "value")
Para escrever respostas, temos três abordagens principais:
// w.Write() - escreve bytes diretamente
w.Write([]byte("texto simples"))
// w.WriteHeader() - define o status code (deve ser chamado antes de Write)
w.WriteHeader(http.StatusCreated)
w.Write([]byte("recurso criado"))
// fmt.Fprintf - formatação flexível
fmt.Fprintf(w, "Usuário %s criado com ID %d", name, id)
5. Gerenciamento de tempo e contextos
O context.Context permite propagar cancelamentos e deadlines através da cadeia de handlers:
func longRunningHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(5 * time.Second):
w.Write([]byte("Operação concluída"))
case <-ctx.Done():
http.Error(w, "Requisição cancelada", http.StatusRequestTimeout)
}
}
Configurar timeouts no servidor é essencial para produção:
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
Para graceful shutdown:
srv := &http.Server{Addr: ":8080"}
go srv.ListenAndServe()
// Aguarda sinal para desligar
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)
6. Servindo conteúdo estático e arquivos
O http.FileServer serve arquivos de um diretório:
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.StripPrefix remove o prefixo da URL antes de passar para o FileServer. Para usar arquivos embutidos (Go 1.16+):
//go:embed static/*
var staticFiles embed.FS
func main() {
fs := http.FileServer(http.FS(staticFiles))
http.Handle("/", fs)
http.ListenAndServe(":8080", nil)
}
7. Tratamento de erros e respostas customizadas
Padrões para retornar códigos de status:
func getUserHandler(w http.ResponseWriter, r *http.Request) {
user, err := findUser(r.PathValue("id"))
if err == sql.ErrNoRows {
http.Error(w, `{"error":"usuário não encontrado"}`, http.StatusNotFound)
return
}
if err != nil {
http.Error(w, `{"error":"erro interno"}`, http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
Helpers para respostas JSON:
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
Middleware de panic recovery:
func recoveryMiddleware(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 do servidor", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
8. Boas práticas e performance
Reutilização de buffers com sync.Pool:
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func handler(w http.ResponseWriter, r *http.Request) {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
// usa buf para processar a requisição
}
Limitação de corpo de requisição:
func uploadHandler(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10 MB
// processa o upload
}
Testando handlers com httptest:
func TestHelloHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/hello", nil)
rec := httptest.NewRecorder()
helloHandler(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("esperado 200, obtido %d", rec.Code)
}
if rec.Body.String() != "Hello!" {
t.Errorf("esperado 'Hello!', obtido '%s'", rec.Body.String())
}
}
O pacote net/http é poderoso e completo, permitindo construir desde APIs simples até servidores de alta performance sem dependências externas. Dominar seus recursos é fundamental para qualquer desenvolvedor Go.
Referências
- Documentação oficial do pacote net/http — Referência completa da API, incluindo tipos, funções e exemplos oficiais.
- Go by Example: HTTP Servers — Tutoriais práticos com exemplos concisos de servidores HTTP em Go.
- Writing Web Applications - Golang Docs — Tutorial oficial do Go para construção de aplicações web completas.
- Middleware Patterns in Go - Alex Edwards — Guia prático sobre criação e composição de middlewares em Go.
- Graceful Shutdown in Go - The Dev Project — Explicação detalhada sobre shutdown gracioso com contextos e sinais do sistema.
- Go 1.22 Routing Enhancements - Go Blog — Novidades do pattern matching e roteamento introduzidas no Go 1.22.