O que é HTTP em Go?
O HTTP (HyperText Transfer Protocol) é o protocolo fundamental da web, e Go oferece suporte completo a ele através do pacote net/http da biblioteca padrão. Diferente de muitas linguagens que dependem de frameworks externos para lidar com HTTP, Go inclui um servidor HTTP de produção pronto para uso — o mesmo tipo de servidor que empresas como Google, Cloudflare e Uber utilizam em seus sistemas de alta escala.
O pacote net/http foi projetado desde o início para ser eficiente, seguro e compatível com concorrência. Cada requisição HTTP é tratada em sua própria goroutine, o que significa que o servidor pode atender milhares de conexões simultâneas sem configuração especial. Essa abordagem é radicalmente diferente de modelos baseados em threads (como Java) ou event loops (como Node.js), e é uma das razões pelas quais Go se tornou a linguagem preferida para desenvolvimento backend.
O pacote suporta HTTP/1.1 e HTTP/2 nativamente, com TLS integrado, e fornece tanto um servidor quanto um cliente HTTP completos. Vamos explorar cada aspecto em detalhes.
Criando um servidor HTTP básico
O caminho mais rápido para criar um servidor HTTP em Go usa http.ListenAndServe:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Olá, Mundo! Método: %s, Path: %s", r.Method, r.URL.Path)
})
http.HandleFunc("/saude", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "OK")
})
fmt.Println("Servidor rodando em :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Printf("Erro ao iniciar servidor: %v\n", err)
}
}
Quando você passa nil como segundo argumento de ListenAndServe, Go usa o DefaultServeMux — o router padrão que registra as rotas definidas com http.HandleFunc. Para aplicações simples isso funciona bem, mas para produção você vai querer um http.Server customizado.
http.Server — configuração para produção
O http.Server permite configurar timeouts, TLS, tamanho máximo de headers e outras opções essenciais para produção:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/usuarios", listarUsuarios)
mux.HandleFunc("GET /api/usuarios/{id}", buscarUsuario)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MB
}
// Graceful shutdown
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
log.Println("Iniciando shutdown graceful...")
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Erro no shutdown: %v", err)
}
}()
log.Printf("Servidor rodando em %s", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Erro fatal: %v", err)
}
log.Println("Servidor encerrado com sucesso")
}
func listarUsuarios(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `[{"id": 1, "nome": "Ana"}, {"id": 2, "nome": "Carlos"}]`)
}
func buscarUsuario(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"id": "%s", "nome": "Usuário %s"}`, id, id)
}
Os timeouts são críticos para evitar ataques slowloris e vazamentos de conexão. Sem eles, um cliente malicioso pode manter conexões abertas indefinidamente, esgotando os recursos do servidor. Consulte o tutorial de segurança em Go para mais práticas de proteção.
http.Request — anatomia de uma requisição
O http.Request contém todos os dados da requisição HTTP recebida:
func exemploRequest(w http.ResponseWriter, r *http.Request) {
// Método HTTP (GET, POST, PUT, DELETE, etc.)
metodo := r.Method
// URL e path
path := r.URL.Path
query := r.URL.Query().Get("busca")
// Path parameters (Go 1.22+)
id := r.PathValue("id")
// Headers
contentType := r.Header.Get("Content-Type")
auth := r.Header.Get("Authorization")
// Body (para POST/PUT)
defer r.Body.Close()
// body, err := io.ReadAll(r.Body)
// Context da requisição
ctx := r.Context()
// Cookies
cookie, err := r.Cookie("session_id")
if err == nil {
fmt.Println("Session:", cookie.Value)
}
// Endereço do cliente
clienteIP := r.RemoteAddr
fmt.Fprintf(w, "Método: %s, Path: %s, Query: %s, ID: %s, IP: %s, Content-Type: %s, Auth presente: %v, Context: %v",
metodo, path, query, id, clienteIP, contentType, auth != "", ctx)
}
O context da requisição (r.Context()) é automaticamente cancelado quando o cliente desconecta, permitindo que operações longas como consultas ao banco sejam interrompidas imediatamente.
http.ResponseWriter — construindo respostas
O http.ResponseWriter é a interface usada para construir a resposta HTTP:
func exemploResponse(w http.ResponseWriter, r *http.Request) {
// Headers devem ser definidos ANTES de WriteHeader ou Write
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Request-Id", "abc-123")
w.Header().Set("Cache-Control", "max-age=3600")
// Status code (deve vir ANTES do body)
w.WriteHeader(http.StatusCreated) // 201
// Body
fmt.Fprint(w, `{"mensagem": "Recurso criado com sucesso"}`)
}
// Resposta com JSON usando encoding/json
func respostaJSON(w http.ResponseWriter, r *http.Request) {
type Resposta struct {
Status string `json:"status"`
Mensagem string `json:"mensagem"`
Codigo int `json:"codigo"`
}
resp := Resposta{
Status: "sucesso",
Mensagem: "Operação realizada",
Codigo: 200,
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, "Erro interno", http.StatusInternalServerError)
}
}
Uma regra importante: a ordem de chamada é Header() > WriteHeader() > Write(). Se você chamar Write() antes de WriteHeader(), Go automaticamente envia o status 200 OK.
HTTP Client — fazendo requisições
O pacote net/http também fornece um cliente HTTP completo:
func exemploCliente() {
// Cliente com timeout (NUNCA use http.DefaultClient em produção)
cliente := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
// GET simples
resp, err := cliente.Get("https://api.exemplo.com/dados")
if err != nil {
log.Fatalf("Erro na requisição: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Erro ao ler body: %v", err)
}
fmt.Printf("Status: %d, Body: %s\n", resp.StatusCode, body)
// POST com JSON
dados := bytes.NewBufferString(`{"nome": "Ana", "email": "ana@exemplo.com"}`)
req, err := http.NewRequestWithContext(
context.Background(),
http.MethodPost,
"https://api.exemplo.com/usuarios",
dados,
)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token-aqui")
resp2, err := cliente.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp2.Body.Close()
fmt.Printf("Criado com status: %d\n", resp2.StatusCode)
}
Nunca use http.DefaultClient em produção — ele não tem timeout configurado, o que significa que uma requisição pode travar para sempre. Sempre crie um cliente com timeouts explícitos. Para mais detalhes sobre construção de APIs, veja o tutorial de API REST em Go e o guia de API REST.
TLS e HTTPS
Go facilita a criação de servidores HTTPS com ListenAndServeTLS:
func servidorHTTPS() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Conexão segura!")
})
srv := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
}
log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
O suporte HTTP/2 é habilitado automaticamente quando TLS está ativo. Para informações sobre conformidade criptográfica, consulte o artigo sobre FIPS 140 em Go.
Status codes HTTP
Go define todas as constantes de status HTTP no pacote net/http:
// Códigos mais usados
http.StatusOK // 200
http.StatusCreated // 201
http.StatusNoContent // 204
http.StatusBadRequest // 400
http.StatusUnauthorized // 401
http.StatusForbidden // 403
http.StatusNotFound // 404
http.StatusMethodNotAllowed // 405
http.StatusInternalServerError // 500
http.StatusServiceUnavailable // 503
// Uso com http.Error para respostas de erro
func handleErro(w http.ResponseWriter, r *http.Request) {
dados, err := buscarDados(r.Context())
if err != nil {
http.Error(w, "Recurso não encontrado", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(dados)
}
Use sempre as constantes nomeadas em vez de números mágicos — o código fica mais legível e menos propenso a erros. Consulte o guia de tratamento de erros em Go para padrões avançados.
Testando servidores HTTP
O pacote net/http/httptest permite testar handlers sem iniciar um servidor real:
func TestListarUsuarios(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/usuarios", nil)
rec := httptest.NewRecorder()
listarUsuarios(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("esperava status 200, recebeu %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "Ana") {
t.Error("resposta deveria conter 'Ana'")
}
}
func TestIntegracao(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/saude", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "OK")
})
srv := httptest.NewServer(mux)
defer srv.Close()
resp, err := http.Get(srv.URL + "/api/saude")
if err != nil {
t.Fatalf("erro na requisição: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("esperava 200, recebeu %d", resp.StatusCode)
}
}
Para padrões avançados de testing, consulte o tutorial completo de testes em Go e o guia de TDD com CI/CD.
Boas práticas para produção
- Sempre configure timeouts — ReadTimeout, WriteTimeout, IdleTimeout
- Use graceful shutdown —
srv.Shutdown(ctx)para drenar conexões ativas - Nunca use DefaultServeMux em produção — crie instâncias dedicadas de
http.ServeMux - Configure logging com slog — log estruturado para cada requisição
- Use middleware para cross-cutting concerns — autenticação, logging, CORS, rate limiting
- Monitore com OpenTelemetry — tracing e métricas de cada requisição
- Reutilize
http.Client— um único cliente compartilhado evita overhead de conexões - Implemente health checks — endpoints
/saudee/prontopara orquestradores como Kubernetes
Perguntas Frequentes
O que é o pacote net/http em Go?
O pacote net/http é a implementação completa de HTTP da biblioteca padrão de Go. Ele fornece servidor HTTP de produção, cliente HTTP, router integrado (ServeMux), suporte a HTTP/2 e TLS, e todas as abstrações necessárias para construir aplicações web. Diferente de outras linguagens, Go não precisa de frameworks externos para servir HTTP em produção com segurança e performance.
Qual a diferença entre http.ListenAndServe e http.Server?
http.ListenAndServe é um atalho que cria um servidor HTTP com configurações padrão. http.Server é uma struct que permite configurar timeouts, TLS, tamanho de headers e outras opções essenciais para produção. Em produção, sempre use http.Server com timeouts explícitos para evitar ataques slowloris e vazamento de conexões.
Go suporta HTTP/2?
Sim, Go suporta HTTP/2 nativamente desde o Go 1.6. O suporte HTTP/2 é habilitado automaticamente quando você usa TLS (HTTPS). Para conexões não-TLS (h2c), é necessário configuração adicional usando o pacote golang.org/x/net/http2/h2c. A maioria das aplicações em produção usa HTTPS, então HTTP/2 funciona sem configuração extra.
Devo usar http.DefaultClient em produção?
Não. O http.DefaultClient não tem timeout configurado, o que significa que uma requisição pode bloquear uma goroutine indefinidamente. Sempre crie um http.Client com Timeout explícito e Transport customizado. Para aplicações com muitas chamadas HTTP, configure MaxIdleConns e MaxIdleConnsPerHost no Transport para reutilizar conexões eficientemente.