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

  1. Sempre configure timeouts — ReadTimeout, WriteTimeout, IdleTimeout
  2. Use graceful shutdownsrv.Shutdown(ctx) para drenar conexões ativas
  3. Nunca use DefaultServeMux em produção — crie instâncias dedicadas de http.ServeMux
  4. Configure logging com slog — log estruturado para cada requisição
  5. Use middleware para cross-cutting concerns — autenticação, logging, CORS, rate limiting
  6. Monitore com OpenTelemetry — tracing e métricas de cada requisição
  7. Reutilize http.Client — um único cliente compartilhado evita overhead de conexões
  8. Implemente health checks — endpoints /saude e /pronto para 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.