<h2>Introdução ao Pacote net/http de Go</h2>
<p>O pacote <code>net/http</code> é uma das bibliotecas padrão mais poderosas do Go, oferecendo suporte completo para construir servidores e clientes HTTP sem dependências externas. Diferentemente de outras linguagens que frequentemente dependem de frameworks pesados, Go fornece primitivas suficientemente baixo nível e bem projetadas para que você possa criar aplicações HTTP escaláveis e eficientes diretamente da stdlib.</p>
<p>A filosofia do Go nesse aspecto é clara: forneça ferramentas robustas, simples e compostas. O pacote <code>net/http</code> segue rigorosamente esse princípio. Neste artigo, você compreenderá como os componentes principais funcionam, desde a anatomia de um servidor HTTP até a execução de requisições de cliente, passando por conceitos como handlers, middlewares e tratamento de erros.</p>
<h2>Fundamentos do Servidor HTTP</h2>
<h3>Conceito e Arquitetura Básica</h3>
<p>Um servidor HTTP em Go é construído sobre o tipo <code>http.Server</code>, que encapsula toda a lógica necessária para escutar conexões TCP, interpretar requisições HTTP e enviar respostas. A forma mais simples de iniciar um servidor é usando <code>http.ListenAndServe()</code>, que combina criação, configuração e escuta em uma única chamada. Por baixo dos panos, Go cria uma goroutine para cada conexão cliente, permitindo que centenas de milhares de requisições simultâneas sejam processadas de forma eficiente.</p>
<p>O conceito fundamental é o <code>http.Handler</code>. Trata-se de uma interface simples com apenas um método: <code>ServeHTTP(ResponseWriter, *Request)</code>. Qualquer tipo que implemente esse método pode processar requisições HTTP. Essa simplicidade é intencional: permite composição, reutilização e testes diretos sem abstração desnecessária.</p>
<pre><code class="language-go">package main
import (
"fmt"
"net/http"
)
// Handler simples que implementa a interface http.Handler
type HelloHandler struct{}
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Olá, %s!", r.URL.Query().Get("nome"))
}
func main() {
handler := &HelloHandler{}
http.ListenAndServe(":8080", handler)
}</code></pre>
<h3>Funções Convenientes e Roteamento Básico</h3>
<p>Para a maioria dos casos reais, você não criará tipos personalizados que implementam <code>http.Handler</code>. Em vez disso, usará <code>http.HandleFunc()</code>, que converte uma função simples em um handler. Essa função recebe os mesmos parâmetros que <code>ServeHTTP()</code> e é transformada internamente em um tipo que implementa a interface.</p>
<pre><code class="language-go">package main
import (
"fmt"
"net/http"
)
func main() {
// Registra handlers diretamente com funções
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, {"mensagem":"Página inicial"})
})
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
return
}
fmt.Fprintf(w, {"usuarios":["Alice","Bob"]})
})
fmt.Println("Servidor rodando em :8080")
http.ListenAndServe(":8080", nil)
}</code></pre>
<p>Quando você passa <code>nil</code> como segundo argumento de <code>ListenAndServe()</code>, Go usa o <code>DefaultServeMux</code>, um multiplexador global que armazena todos os handlers registrados via <code>http.HandleFunc()</code> e <code>http.Handle()</code>. Para aplicações maiores, crie um <code>ServeMux</code> personalizado para evitar efeitos colaterais globais.</p>
<h3>Criando Middlewares Robustos</h3>
<p>Middlewares são funções que envolvem handlers, adicionando comportamentos como logging, autenticação, CORS ou compressão. A forma idiomática em Go é usar uma função que recebe um <code>http.Handler</code> e retorna outro <code>http.Handler</code>.</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"net/http"
"time"
)
// Middleware de logging que registra informações sobre cada requisição
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
log.Printf("[%s] %s %s", r.Method, r.RequestURI, r.RemoteAddr)
next.ServeHTTP(w, r)
duracao := time.Since(inicio)
log.Printf("Requisição processada em %v", duracao)
})
}
// Middleware de autenticação simples
func autenticacaoMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer secret-token-123" {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// Função auxiliar para encadear middlewares
func comMiddlewares(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func main() {
mux := http.NewServeMux()
// Handler principal
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Dados sensíveis retornados com sucesso")
})
// Aplica middlewares na ordem desejada
mux.Handle("/dados", comMiddlewares(
handler,
logMiddleware,
autenticacaoMiddleware,
))
log.Fatal(http.ListenAndServe(":8080", mux))
}</code></pre>
<h2>Cliente HTTP em Go</h2>
<h3>Fazendo Requisições Básicas</h3>
<p>O pacote <code>net/http</code> fornece o tipo <code>http.Client</code>, que representa um cliente HTTP reutilizável. A abordagem padrão é criar um cliente uma única vez e reutilizá-lo para múltiplas requisições, pois ele gerencia pool de conexões internamente, economizando recursos e melhorando performance.</p>
<pre><code class="language-go">package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// Criar um cliente HTTP
client := &http.Client{}
// Fazer uma requisição GET simples
resp, err := client.Get("https://api.github.com/users/golang")
if err != nil {
fmt.Printf("Erro na requisição: %v\n", err)
return
}
defer resp.Body.Close()
// Ler o corpo da resposta
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Erro ao ler resposta: %v\n", err)
return
}
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Headers: %v\n", resp.Header)
fmt.Printf("Body: %s\n", string(body))
}</code></pre>
<h3>Requisições Customizadas com http.Request</h3>
<p>Para casos onde você precisa controlar detalhes como headers personalizados, método HTTP específico ou timeout, construa uma requisição manualmente usando <code>http.NewRequest()</code>. Isso oferece granularidade completa sobre o que é enviado ao servidor.</p>
<pre><code class="language-go">package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
// Dados que serão enviados no corpo da requisição
dados := map[string]interface{}{
"titulo": "Novo Post",
"corpo": "Este é um exemplo de POST com JSON",
}
// Serializar para JSON
payload, _ := json.Marshal(dados)
// Criar requisição manualmente
req, err := http.NewRequest(
http.MethodPost,
"https://api.exemplo.com/posts",
bytes.NewBuffer(payload),
)
if err != nil {
fmt.Printf("Erro ao criar requisição: %v\n", err)
return
}
// Adicionar headers customizados
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token-123")
req.Header.Set("User-Agent", "MeuApp/1.0")
// Executar a requisição
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Erro ao executar requisição: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("Status: %d\n", resp.StatusCode)
}</code></pre>
<h3>Tratamento de Timeouts e Resiliência</h3>
<p>Em ambientes de produção, timeouts são essenciais para evitar que seu programa fica preso esperando por servidores lentos ou não responsivos. Configure timeouts no <code>http.Client</code> usando <code>time.Duration</code>.</p>
<pre><code class="language-go">package main
import (
"fmt"
"io"
"net/http"
"time"
)
func main() {
// Cliente com timeouts configurados
client := &http.Client{
Timeout: 5 * time.Second,
}
// Você também pode configurar timeouts específicos
client.Timeout = 0 // Desabilita timeout global
transport := &http.Transport{
DialTimeout: 3 * time.Second, // Timeout para conectar
TLSHandshakeTimeout: 2 * time.Second, // Timeout para handshake TLS
IdleConnTimeout: 30 * time.Second, // Timeout de inatividade
}
client.Transport = transport
// Usar o cliente com retry simples
maxRetries := 3
var resp *http.Response
var err error
for tentativa := 1; tentativa <= maxRetries; tentativa++ {
resp, err = client.Get("https://httpbin.org/delay/2")
if err == nil {
break
}
fmt.Printf("Tentativa %d falhou: %v\n", tentativa, err)
if tentativa < maxRetries {
time.Sleep(time.Second * time.Duration(tentativa))
}
}
if err != nil {
fmt.Printf("Todas as tentativas falharam: %v\n", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Resposta recebida: %d bytes\n", len(body))
}</code></pre>
<h2>Padrões Avançados e Boas Práticas</h2>
<h3>Context para Cancelamento e Deadlines</h3>
<p>O pacote <code>context</code> é fundamental para gerenciar ciclos de vida de requisições em Go. Use-o para implementar cancelamento, deadlines e passar valores através da cadeia de funções de forma segura e explícita.</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func main() {
// Criar um contexto com deadline
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// Criar requisição associada ao contexto
req, _ := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://httpbin.org/delay/5",
nil,
)
// Executar requisição
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Erro: %v\n", err) // Timeout será respeitado
return
}
defer resp.Body.Close()
io.ReadAll(resp.Body)
fmt.Println("Sucesso")
}</code></pre>
<p>No lado do servidor, você também pode usar <code>context</code> para passar valores entre middlewares:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"net/http"
)
type ContextKey string
const UserIDKey ContextKey = "user_id"
// Middleware que extrai user_id do header e armazena no context
func extrairUserMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-ID")
ctx := context.WithValue(r.Context(), UserIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func main() {
mux := http.NewServeMux()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Recuperar user_id do context
userID := r.Context().Value(UserIDKey).(string)
fmt.Fprintf(w, "User ID: %s", userID)
})
mux.Handle("/perfil", extrairUserMiddleware(handler))
http.ListenAndServe(":8080", mux)
}</code></pre>
<h3>Error Handling e Status Codes Apropriados</h3>
<p>A função <code>http.Error()</code> é conveniente, mas em aplicações reais você frequentemente quer retornar JSON estruturado. Crie helpers que padronizem suas respostas de erro.</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"net/http"
)
// Estrutura padrão para respostas de erro
type ErrorResponse struct {
Erro string json:"erro"
Codigo int json:"codigo"
Detalhe string json:"detalhe,omitempty"
}
// Helper para retornar erros em JSON
func responderErro(w http.ResponseWriter, statusCode int, mensagem string, detalhe string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
resposta := ErrorResponse{
Erro: mensagem,
Codigo: statusCode,
Detalhe: detalhe,
}
json.NewEncoder(w).Encode(resposta)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/recurso", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
responderErro(w, http.StatusMethodNotAllowed, "Método não permitido", "Apenas GET é aceito")
return
}
id := r.URL.Query().Get("id")
if id == "" {
responderErro(w, http.StatusBadRequest, "Parâmetro ausente", "Parâmetro 'id' é obrigatório")
return
}
// Simular busca de recurso
if id != "123" {
responderErro(w, http.StatusNotFound, "Recurso não encontrado", fmt.Sprintf("ID %s não existe", id))
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id, "nome": "Recurso"})
})
http.ListenAndServe(":8080", mux)
}</code></pre>
<h2>Conclusão</h2>
<p>Durante este artigo, você aprendeu três conceitos fundamentais que formam a base para trabalhar com HTTP em Go. Primeiro, compreendeu que um servidor HTTP em Go é construído sobre a elegante interface <code>http.Handler</code>, permitindo composição simples através de middlewares sem frameworks pesados. Segundo, viu que o cliente HTTP (<code>http.Client</code>) deve ser reutilizado e configurado com devida atenção a timeouts e resiliência. Terceiro, internalizou que padrões idiomáticos como <code>context</code> para cancelamento e estruturas de erro padronizadas não são luxos, mas necessidades reais em código de produção.</p>
<p>O pacote <code>net/http</code> de Go é deliberadamente minimalista, mas essa aparente simplicidade mascara um design profundo. Use-o sem medo de que você está reinventando a roda — o Go fez esse trabalho fundamental tão bem que raramente você precisará de dependências externas para HTTP. A chave está em entender seus primitivos e aprender a combiná-los efetivamente.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://pkg.go.dev/net/http" target="_blank" rel="noopener noreferrer">Package net/http — Go Documentation</a></li>
<li><a href="https://go.dev/doc/effective_go#web_servers" target="_blank" rel="noopener noreferrer">Effective Go — net/http examples</a></li>
<li><a href="https://youtu.be/rWnD8RcWrFw" target="_blank" rel="noopener noreferrer">Building Web Applications with Go — Andrew Gerrand</a></li>
<li><a href="https://pkg.go.dev/context" target="_blank" rel="noopener noreferrer">Context Package — Go Documentation</a></li>
<li><a href="https://www.goinggo.net/2013/11/using-interfaces-effectively.html" target="_blank" rel="noopener noreferrer">Writing HTTP Middleware in Go — Mat Ryer</a></li>
</ul>
<p><!-- FIM --></p>