Go

Pacote net/http em Go: Servidor e Cliente HTTP da Stdlib na Prática

15 min de leitura

Pacote net/http em Go: Servidor e Cliente HTTP da Stdlib na Prática

Introdução ao Pacote net/http de Go O pacote é 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. A filosofia do Go nesse aspecto é clara: forneça ferramentas robustas, simples e compostas. O pacote 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. Fundamentos do Servidor HTTP Conceito e Arquitetura Básica Um servidor HTTP em Go é construído sobre o tipo , 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 ,

<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 (

&quot;fmt&quot;

&quot;net/http&quot;

)

// Handler simples que implementa a interface http.Handler

type HelloHandler struct{}

func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;text/plain; charset=utf-8&quot;)

fmt.Fprintf(w, &quot;Olá, %s!&quot;, r.URL.Query().Get(&quot;nome&quot;))

}

func main() {

handler := &amp;HelloHandler{}

http.ListenAndServe(&quot;:8080&quot;, 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 (

&quot;fmt&quot;

&quot;net/http&quot;

)

func main() {

// Registra handlers diretamente com funções

http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

fmt.Fprintf(w, {&quot;mensagem&quot;:&quot;Página inicial&quot;})

})

http.HandleFunc(&quot;/users&quot;, func(w http.ResponseWriter, r *http.Request) {

if r.Method != http.MethodGet {

http.Error(w, &quot;Método não permitido&quot;, http.StatusMethodNotAllowed)

return

}

fmt.Fprintf(w, {&quot;usuarios&quot;:[&quot;Alice&quot;,&quot;Bob&quot;]})

})

fmt.Println(&quot;Servidor rodando em :8080&quot;)

http.ListenAndServe(&quot;:8080&quot;, 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 (

&quot;fmt&quot;

&quot;log&quot;

&quot;net/http&quot;

&quot;time&quot;

)

// 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(&quot;[%s] %s %s&quot;, r.Method, r.RequestURI, r.RemoteAddr)

next.ServeHTTP(w, r)

duracao := time.Since(inicio)

log.Printf(&quot;Requisição processada em %v&quot;, 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(&quot;Authorization&quot;)

if token != &quot;Bearer secret-token-123&quot; {

http.Error(w, &quot;Não autorizado&quot;, 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 &gt;= 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, &quot;Dados sensíveis retornados com sucesso&quot;)

})

// Aplica middlewares na ordem desejada

mux.Handle(&quot;/dados&quot;, comMiddlewares(

handler,

logMiddleware,

autenticacaoMiddleware,

))

log.Fatal(http.ListenAndServe(&quot;:8080&quot;, 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 (

&quot;fmt&quot;

&quot;io&quot;

&quot;net/http&quot;

)

func main() {

// Criar um cliente HTTP

client := &amp;http.Client{}

// Fazer uma requisição GET simples

resp, err := client.Get(&quot;https://api.github.com/users/golang&quot;)

if err != nil {

fmt.Printf(&quot;Erro na requisição: %v\n&quot;, err)

return

}

defer resp.Body.Close()

// Ler o corpo da resposta

body, err := io.ReadAll(resp.Body)

if err != nil {

fmt.Printf(&quot;Erro ao ler resposta: %v\n&quot;, err)

return

}

fmt.Printf(&quot;Status: %d\n&quot;, resp.StatusCode)

fmt.Printf(&quot;Headers: %v\n&quot;, resp.Header)

fmt.Printf(&quot;Body: %s\n&quot;, 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 (

&quot;bytes&quot;

&quot;encoding/json&quot;

&quot;fmt&quot;

&quot;net/http&quot;

)

func main() {

// Dados que serão enviados no corpo da requisição

dados := map[string]interface{}{

&quot;titulo&quot;: &quot;Novo Post&quot;,

&quot;corpo&quot;: &quot;Este é um exemplo de POST com JSON&quot;,

}

// Serializar para JSON

payload, _ := json.Marshal(dados)

// Criar requisição manualmente

req, err := http.NewRequest(

http.MethodPost,

&quot;https://api.exemplo.com/posts&quot;,

bytes.NewBuffer(payload),

)

if err != nil {

fmt.Printf(&quot;Erro ao criar requisição: %v\n&quot;, err)

return

}

// Adicionar headers customizados

req.Header.Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

req.Header.Set(&quot;Authorization&quot;, &quot;Bearer token-123&quot;)

req.Header.Set(&quot;User-Agent&quot;, &quot;MeuApp/1.0&quot;)

// Executar a requisição

client := &amp;http.Client{}

resp, err := client.Do(req)

if err != nil {

fmt.Printf(&quot;Erro ao executar requisição: %v\n&quot;, err)

return

}

defer resp.Body.Close()

fmt.Printf(&quot;Status: %d\n&quot;, 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 (

&quot;fmt&quot;

&quot;io&quot;

&quot;net/http&quot;

&quot;time&quot;

)

func main() {

// Cliente com timeouts configurados

client := &amp;http.Client{

Timeout: 5 * time.Second,

}

// Você também pode configurar timeouts específicos

client.Timeout = 0 // Desabilita timeout global

transport := &amp;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 &lt;= maxRetries; tentativa++ {

resp, err = client.Get(&quot;https://httpbin.org/delay/2&quot;)

if err == nil {

break

}

fmt.Printf(&quot;Tentativa %d falhou: %v\n&quot;, tentativa, err)

if tentativa &lt; maxRetries {

time.Sleep(time.Second * time.Duration(tentativa))

}

}

if err != nil {

fmt.Printf(&quot;Todas as tentativas falharam: %v\n&quot;, err)

return

}

defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

fmt.Printf(&quot;Resposta recebida: %d bytes\n&quot;, 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 (

&quot;context&quot;

&quot;fmt&quot;

&quot;io&quot;

&quot;net/http&quot;

&quot;time&quot;

)

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,

&quot;https://httpbin.org/delay/5&quot;,

nil,

)

// Executar requisição

client := &amp;http.Client{}

resp, err := client.Do(req)

if err != nil {

fmt.Printf(&quot;Erro: %v\n&quot;, err) // Timeout será respeitado

return

}

defer resp.Body.Close()

io.ReadAll(resp.Body)

fmt.Println(&quot;Sucesso&quot;)

}</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 (

&quot;context&quot;

&quot;fmt&quot;

&quot;net/http&quot;

)

type ContextKey string

const UserIDKey ContextKey = &quot;user_id&quot;

// 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(&quot;X-User-ID&quot;)

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, &quot;User ID: %s&quot;, userID)

})

mux.Handle(&quot;/perfil&quot;, extrairUserMiddleware(handler))

http.ListenAndServe(&quot;:8080&quot;, 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 (

&quot;encoding/json&quot;

&quot;fmt&quot;

&quot;net/http&quot;

)

// Estrutura padrão para respostas de erro

type ErrorResponse struct {

Erro string json:&quot;erro&quot;

Codigo int json:&quot;codigo&quot;

Detalhe string json:&quot;detalhe,omitempty&quot;

}

// Helper para retornar erros em JSON

func responderErro(w http.ResponseWriter, statusCode int, mensagem string, detalhe string) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

w.WriteHeader(statusCode)

resposta := ErrorResponse{

Erro: mensagem,

Codigo: statusCode,

Detalhe: detalhe,

}

json.NewEncoder(w).Encode(resposta)

}

func main() {

mux := http.NewServeMux()

mux.HandleFunc(&quot;/recurso&quot;, func(w http.ResponseWriter, r *http.Request) {

if r.Method != http.MethodGet {

responderErro(w, http.StatusMethodNotAllowed, &quot;Método não permitido&quot;, &quot;Apenas GET é aceito&quot;)

return

}

id := r.URL.Query().Get(&quot;id&quot;)

if id == &quot;&quot; {

responderErro(w, http.StatusBadRequest, &quot;Parâmetro ausente&quot;, &quot;Parâmetro &#039;id&#039; é obrigatório&quot;)

return

}

// Simular busca de recurso

if id != &quot;123&quot; {

responderErro(w, http.StatusNotFound, &quot;Recurso não encontrado&quot;, fmt.Sprintf(&quot;ID %s não existe&quot;, id))

return

}

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]string{&quot;id&quot;: id, &quot;nome&quot;: &quot;Recurso&quot;})

})

http.ListenAndServe(&quot;:8080&quot;, 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo
Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo

Entendendo Interfaces em Go Interfaces em Go são um dos conceitos mais podero...

O que Todo Dev Deve Saber sobre Mocks em Go: Interfaces, testify/mock e mockery
O que Todo Dev Deve Saber sobre Mocks em Go: Interfaces, testify/mock e mockery

Por que Testamos com Mocks? Testes unitários são o alicerce de um código conf...

Boas Práticas de Fuzzing em Go: Testes Baseados em Propriedades com go test -fuzz para Times Ágeis
Boas Práticas de Fuzzing em Go: Testes Baseados em Propriedades com go test -fuzz para Times Ágeis

O que é Fuzzing e por que você deveria se importar Fuzzing é uma técnica de t...