Programação HTTP

Lição 22: Programação HTTP

🎯 Analogia da Vida

Imagine que você administra um restaurante:

O pacote net/http do Go é seu "sistema de gerenciamento de restaurante", ajudando você a construir rapidamente um serviço web de alto desempenho e estável.


📚 Conceitos Centrais

Conceito Descrição
http.Get/Post Enviar requisições HTTP para obter recursos remotos
http.ListenAndServe Iniciar um servidor HTTP e escutar em uma porta
Interface Handler A interface central que define os padrões de manipulação de requisições
HandlerFunc Um adaptador que converte uma função regular em um Handler
ServeMux Multiplexador de requisições HTTP (roteador)
Middleware Um padrão para inserir lógica comum antes e depois da manipulação de requisições

📝 Sintaxe Básica e Uso

1. Enviando Requisições HTTP

GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	// Enviar requisição GET
	resp, err := http.Get("https://httpbin.org/get")
	if err != nil {
		fmt.Println("Requisição falhou:", err)
		return
	}
	defer resp.Body.Close() // 💡 Deve fechar o corpo da resposta para evitar vazamentos de conexão

	// Ler conteúdo da resposta
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Falha na leitura:", err)
		return
	}

	fmt.Println("Código de status:", resp.StatusCode)
	fmt.Println("Resposta:", string(body))
}
💡 Dica: resp.Body deve ser fechado após o uso com Close(), caso contrário causará vazamentos de conexão. Usar defer é a melhor prática.

2. Enviando Requisições POST

GO
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	// Construir dados JSON
	data := map[string]string{
		"username": "gopher",
		"email":    "gopher@example.com",
	}
	jsonData, _ := json.Marshal(data)

	// Enviar requisição POST
	// 💡 O terceiro parâmetro é o corpo da requisição, requer tipo io.Reader
	resp, err := http.Post(
		"https://httpbin.org/post",
		"application/json",
		bytes.NewBuffer(jsonData),
	)
	if err != nil {
		fmt.Println("Requisição falhou:", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println("Código de status:", resp.StatusCode)
	fmt.Println("Resposta:", string(body))
}
💡 Dica: O parâmetro Content-Type de http.Post é muito importante — o servidor depende dele para analisar o formato do corpo da requisição.

3. Iniciando um Servidor HTTP

GO
package main

import (
	"fmt"
	"net/http"
)

func main() {
	// Registrar manipuladores de rotas
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Bem-vindo à página inicial!")
	})

	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Olá, Gopher!")
	})

	// Iniciar servidor, escutar na porta 8080
	// 💡 ListenAndServe bloqueia até o servidor ser desligado
	fmt.Println("Servidor iniciado em http://localhost:8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("Servidor falhou ao iniciar:", err)
	}
}
💡 Dica: Passar nil como segundo parâmetro significa usar o DefaultServeMux padrão. Em produção, é recomendado criar um roteador personalizado.

4. Interface Handler

GO
// Definição da interface Handler: qualquer tipo que implemente o método ServeHTTP é um Handler
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
GO
package main

import (
	"fmt"
	"net/http"
)

// Tipo Handler personalizado
type GreetingHandler struct {
	Message string
}

// Implementar o método ServeHTTP da interface Handler
func (g GreetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, g.Message)
}

func main() {
	// Usar Handler personalizado
	handler := GreetingHandler{Message: "Olá, este é um Handler personalizado!"}
	http.Handle("/greet", handler) // 💡 Nota: use Handle, não HandleFunc

	http.ListenAndServe(":8080", nil)
}
💡 Dica: Handle aceita uma interface Handler, HandleFunc aceita uma função. Ambos são funcionalmente equivalentes, apenas formas diferentes.

5. Adaptador HandlerFunc

GO
package main

import (
	"fmt"
	"net/http"
)

func myHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Handler adaptado via HandlerFunc")
}

func main() {
	// HandlerFunc converte uma função regular em um Handler
	// 💡 Essencialmente uma conversão de tipo: type HandlerFunc func(ResponseWriter, *Request)
	http.Handle("/adapted", http.HandlerFunc(myHandler))

	// Forma equivalente (mais comumente usada)
	http.HandleFunc("/simple", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Usar HandleFunc diretamente é mais conciso")
	})

	http.ListenAndServe(":8080", nil)
}
💡 Dica: HandlerFunc é um adaptador de tipo que permite que funções regulares satisfaçam a interface Handler.


🧪 Exemplos Práticos

Exemplo: Servidor de Arquivos Estáticos Simples (Dificuldade ⭐)

GO
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	// Registrar serviço de arquivos estáticos usando o roteador padrão
	// StripPrefix remove o prefixo "/static/" da URL
	// FileServer fornece acesso aos arquivos no diretório
	fs := http.FileServer(http.Dir("./public"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	// Manipulador da página inicial
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			http.NotFound(w, r) // 💡 Tratamento 404
			return
		}
		fmt.Fprintf(w, "Bem-vindo ao meu site!")
	})

	fmt.Println("Servidor rodando em http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
▶ Experimente

Execução:

BASH
# Criar diretório e arquivos de teste
mkdir -p public
echo "<h1>Olá</h1>" > public/index.html

# Executar o servidor
go run main.go

# Testar acesso (outro terminal)
curl http://localhost:8080/
curl http://localhost:8080/static/index.html

Exemplo: Roteamento Personalizado e Middleware (Dificuldade ⭐⭐)

GO
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// Middleware de logging: registra o tempo de processamento de cada requisição
func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		log.Printf("Início %s %s", r.Method, r.URL.Path)

		next.ServeHTTP(w, r) // 💡 Chamar o próximo handler

		log.Printf("Fim %s %s levou %v", r.Method, r.URL.Path, time.Since(start))
	})
}

// Middleware de autenticação: verifica Token nos cabeçalhos da requisição
func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("Authorization")
		if token == "" {
			http.Error(w, "Acesso não autorizado", http.StatusUnauthorized)
			return
		}
		log.Printf("Token do usuário: %s", token)
		next.ServeHTTP(w, r)
	})
}

func main() {
	mux := http.NewServeMux() // 💡 Criar roteador personalizado

	// Rotas públicas
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Bem-vindo ao serviço de API!")
	})

	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, `{"status": "ok"}`)
	})

	// Rotas que requerem autenticação
	protected := http.NewServeMux()
	protected.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Esta é sua página de perfil")
	})
	protected.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Esta é a página de configurações")
	})

	// 💡 Cadeia de middleware: logging primeiro, depois autenticação, depois manipulação
	mux.Handle("/api/", AuthMiddleware(protected))

	// Aplicar middleware de logging a todas as rotas
	finalHandler := LoggingMiddleware(mux)

	fmt.Println("Servidor rodando em http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", finalHandler))
}
▶ Experimente

Execução e Teste:

BASH
# Executar
go run main.go

# Testar rotas públicas
curl http://localhost:8080/
curl http://localhost:8080/health

# Testar rotas autenticadas (sem Token)
curl http://localhost:8080/api/profile
# Saída: Acesso não autorizado

# Testar rotas autenticadas (com Token)
curl -H "Authorization: Bearer mytoken123" http://localhost:8080/api/profile
# Saída: Esta é sua página de perfil

Exemplo: Serviço de API RESTful Completo (Dificuldade ⭐⭐⭐)

GO
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

// Modelo User
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

// APIResponse formato de resposta unificado
type APIResponse struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// UserStore armazenamento de usuários (usando map para simular banco de dados)
type UserStore struct {
	mu     sync.RWMutex
	users  map[int]*User
	nextID int
}

func NewUserStore() *UserStore {
	return &UserStore{
		users:  make(map[int]*User),
		nextID: 1,
	}
}

func (s *UserStore) Create(name, email string) *User {
	s.mu.Lock()
	defer s.mu.Unlock()

	user := &User{
		ID:    s.nextID,
		Name:  name,
		Email: email,
	}
	s.users[s.nextID] = user
	s.nextID++
	return user
}

func (s *UserStore) Get(id int) *User {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.users[id]
}

func (s *UserStore) List() []*User {
	s.mu.RLock()
	defer s.mu.RUnlock()

	users := make([]*User, 0, len(s.users))
	for _, u := range s.users {
		users = append(users, u)
	}
	return users
}

func (s *UserStore) Update(id int, name, email string) *User {
	s.mu.Lock()
	defer s.mu.Unlock()

	user, ok := s.users[id]
	if !ok {
		return nil
	}
	if name != "" {
		user.Name = name
	}
	if email != "" {
		user.Email = email
	}
	return user
}

func (s *UserStore) Delete(id int) bool {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.users[id]; !ok {
		return false
	}
	delete(s.users, id)
	return true
}

// UserHandler manipulador da API de usuários
type UserHandler struct {
	store *UserStore
}

// writeJSON escreve uma resposta JSON
func (h *UserHandler) writeJSON(w http.ResponseWriter, code int, resp APIResponse) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(resp)
}

// List obter lista de usuários GET /users
func (h *UserHandler) List(w http.ResponseWriter, r *http.Request) {
	users := h.store.List()
	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "success",
		Data:    users,
	})
}

// Create criar usuário POST /users
func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "Dados de requisição inválidos",
		})
		return
	}

	if req.Name == "" || req.Email == "" {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    2,
			Message: "Nome e email não podem ser vazios",
		})
		return
	}

	user := h.store.Create(req.Name, req.Email)
	h.writeJSON(w, http.StatusCreated, APIResponse{
		Code:    0,
		Message: "Criado com sucesso",
		Data:    user,
	})
}

// Get obter único usuário GET /users/{id}
func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "ID de usuário inválido",
		})
		return
	}

	user := h.store.Get(id)
	if user == nil {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "Usuário não encontrado",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "success",
		Data:    user,
	})
}

// Update atualizar usuário PUT /users/{id}
func (h *UserHandler) Update(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "ID de usuário inválido",
		})
		return
	}

	var req struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "Dados de requisição inválidos",
		})
		return
	}

	user := h.store.Update(id, req.Name, req.Email)
	if user == nil {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "Usuário não encontrado",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "Atualizado com sucesso",
		Data:    user,
	})
}

// Delete excluir usuário DELETE /users/{id}
func (h *UserHandler) Delete(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "ID de usuário inválido",
		})
		return
	}

	if !h.store.Delete(id) {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "Usuário não encontrado",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "Excluído com sucesso",
	})
}

// Middleware CORS
func CORSMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}

		next.ServeHTTP(w, r)
	})
}

// Middleware de recuperação: captura panics
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 capturado: %v", err)
				http.Error(w, "Erro interno do servidor", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func main() {
	store := NewUserStore()
	handler := &UserHandler{store: store}

	// Criar roteador
	mux := http.NewServeMux()

	// 💡 Registrar rotas: despachar para diferentes manipuladores com base no método HTTP
	mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			handler.List(w, r)
		case http.MethodPost:
			handler.Create(w, r)
		default:
			http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
		}
	})

	// 💡 Manipular rotas com parâmetros de caminho /users/{id}
	mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			handler.Get(w, r)
		case http.MethodPut:
			handler.Update(w, r)
		case http.MethodDelete:
			handler.Delete(w, r)
		default:
			http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
		}
	})

	// Aplicar cadeia de middleware
	finalHandler := RecoveryMiddleware(CORSMiddleware(mux))

	// Criar servidor (timeouts configuráveis)
	server := &http.Server{
		Addr:         ":8080",
		Handler:      finalHandler,
		ReadTimeout:  10 * time.Second,  // 💡 Timeout de leitura
		WriteTimeout: 10 * time.Second,  // 💡 Timeout de escrita
	}

	// Inserir dados de teste
	store.Create("Alice", "alice@example.com")
	store.Create("Bob", "bob@example.com")

	fmt.Println("Servidor da API RESTful rodando em http://localhost:8080")
	fmt.Println("Endpoints disponíveis:")
	fmt.Println("  GET    /users      - Obter lista de usuários")
	fmt.Println("  POST   /users      - Criar usuário")
	fmt.Println("  GET    /users/{id} - Obter único usuário")
	fmt.Println("  PUT    /users/{id} - Atualizar usuário")
	fmt.Println("  DELETE /users/{id} - Excluir usuário")

	log.Fatal(server.ListenAndServe())
}
▶ Experimente

Execução e Teste Completo:

BASH
# Executar servidor
go run main.go

# Obter lista de usuários
curl http://localhost:8080/users

# Criar usuário
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Charlie", "email": "charlie@example.com"}'

# Obter único usuário
curl http://localhost:8080/users/1

# Atualizar usuário
curl -X PUT http://localhost:8080/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Smith"}'

# Excluir usuário
curl -X DELETE http://localhost:8080/users/3

🎬 Cenários Práticos

Cenário 1: Construindo um Serviço de Consulta do Clima

GO
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

// WeatherResponse resposta simulada da API de clima
type WeatherResponse struct {
	City        string  `json:"city"`
	Temperature float64 `json:"temperature"`
	Condition   string  `json:"condition"`
	Humidity    int     `json:"humidity"`
}

// WeatherService manipulador do serviço de clima
type WeatherService struct {
	data map[string]WeatherResponse
}

func NewWeatherService() *WeatherService {
	return &WeatherService{
		data: map[string]WeatherResponse{
			"sao_paulo": {City: "São Paulo", Temperature: 28.5, Condition: "Ensolarado", Humidity: 45},
			"rio":       {City: "Rio de Janeiro", Temperature: 30.2, Condition: "Nublado", Humidity: 65},
			"curitiba":  {City: "Curitiba", Temperature: 18.1, Condition: "Tempestade", Humidity: 80},
		},
	}
}

// GetWeather consultar clima GET /weather?city=sao_paulo
func (s *WeatherService) GetWeather(w http.ResponseWriter, r *http.Request) {
	// 💡 Obter nome da cidade dos parâmetros de consulta da URL
	city := r.URL.Query().Get("city")
	if city == "" {
		http.Error(w, `{"error": "Por favor, forneça o parâmetro city"}`, http.StatusBadRequest)
		return
	}

	city, _ = url.QueryUnescape(city)

	weather, ok := s.data[city]
	if !ok {
		http.Error(w, fmt.Sprintf(`{"error": "Cidade não encontrada: %s"}`, city), http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(weather)
}

// GetForecast lista de previsões GET /forecast
func (s *WeatherService) GetForecast(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	forecasts := make([]WeatherResponse, 0, len(s.data))
	for _, v := range s.data {
		forecasts = append(forecasts, v)
	}

	json.NewEncoder(w).Encode(map[string]interface{}{
		"count":     len(forecasts),
		"forecasts": forecasts,
	})
}

func main() {
	service := NewWeatherService()

	mux := http.NewServeMux()
	mux.HandleFunc("/weather", service.GetWeather)
	mux.HandleFunc("/forecast", service.GetForecast)

	fmt.Println("Serviço de clima rodando em http://localhost:8080")
	fmt.Println("Uso:")
	fmt.Println("  GET /weather?city=sao_paulo")
	fmt.Println("  GET /forecast")

	http.ListenAndServe(":8080", mux)
}
BASH
# Testar
curl "http://localhost:8080/weather?city=sao_paulo"
# {"city":"São Paulo","temperature":28.5,"condition":"Ensolarado","humidity":45}

curl http://localhost:8080/forecast
# {"count":3,"forecasts":[...]}

Cenário 2: Implementando Middleware de Limitação de Taxa

GO
package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

// RateLimiter limitador de taxa com balde de tokens
type RateLimiter struct {
	mu       sync.Mutex
	tokens   map[string]int
	limit    int
	interval time.Duration
	lastTick map[string]time.Time
}

func NewRateLimiter(limit int, interval time.Duration) *RateLimiter {
	return &RateLimiter{
		tokens:   make(map[string]int),
		limit:    limit,
		interval: interval,
		lastTick: make(map[string]time.Time),
	}
}

// Allow verifica se uma requisição é permitida
func (rl *RateLimiter) Allow(key string) bool {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	now := time.Now()
	last, exists := rl.lastTick[key]

	if !exists || now.Sub(last) >= rl.interval {
		rl.tokens[key] = rl.limit
		rl.lastTick[key] = now
	}

	if rl.tokens[key] <= 0 {
		return false
	}

	rl.tokens[key]--
	return true
}

// RateLimitMiddleware middleware de limitação de taxa
func RateLimitMiddleware(limiter *RateLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			clientIP := r.RemoteAddr

			if !limiter.Allow(clientIP) {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusTooManyRequests) // 429
				fmt.Fprintf(w, `{"error": "Muitas requisições, tente novamente mais tarde"}`)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func main() {
	limiter := NewRateLimiter(10, time.Minute)

	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Requisição bem-sucedida! Hora: %s", time.Now().Format("15:04:05"))
	})

	mux.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, `{"data": "Estes são dados da API", "hora": "%s"}`, time.Now().Format("15:04:05"))
	})

	handler := RateLimitMiddleware(limiter)(mux)

	fmt.Println("Servidor de limitação de taxa rodando em http://localhost:8080")
	fmt.Println("Regra de limite: máximo 10 requisições por minuto")
	http.ListenAndServe(":8080", handler)
}

❓ Perguntas Frequentes

P1: Qual a diferença entre http.HandleFunc e http.Handle?

GO
// HandleFunc aceita uma função
http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá")
})

// Handle aceita uma interface Handler
type MyHandler struct{}
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá")
}
http.Handle("/path", MyHandler{})

Diferença:


P2: Como obter parâmetros de requisição em uma função manipuladora?

GO
func handler(w http.ResponseWriter, r *http.Request) {
	// 💡 Parâmetros de consulta (?key=value na URL)
	name := r.URL.Query().Get("name")

	// 💡 Dados do formulário (POST form-urlencoded)
	r.ParseForm()
	email := r.Form.Get("email")

	// 💡 Parâmetros de caminho (necessário analisar manualmente ou usar roteador de terceiros)
	path := r.URL.Path

	// 💡 Cabeçalhos da requisição
	token := r.Header.Get("Authorization")

	// 💡 Corpo da requisição JSON
	var data map[string]string
	json.NewDecoder(r.Body).Decode(&data)

	fmt.Fprintf(w, "name=%s, email=%s, token=%s", name, email, token)
}

P3: Como implementar desligamento gracioso do servidor?

GO
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	server := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			time.Sleep(2 * time.Second) // Simular operação demorada
			fmt.Fprintf(w, "Processamento concluído")
		}),
	}

	// Iniciar servidor em uma goroutine
	go func() {
		fmt.Println("Servidor iniciado em :8080")
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			log.Fatalf("Erro no servidor: %v", err)
		}
	}()

	// 💡 Escutar sinais do sistema
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit // Bloquear e aguardar sinal

	fmt.Println("\nDesligando servidor graciosamente...")

	// 💡 Dar 5 segundos para requisições existentes completarem
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		log.Fatalf("Falha no desligamento: %v", err)
	}

	fmt.Println("Servidor desligado com segurança")
}

P4: Por que é recomendado passar um Mux personalizado como segundo parâmetro para ListenAndServe?

GO
// ❌ Usando DefaultServeMux padrão (não recomendado)
http.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", nil)

// ✅ Usando Mux personalizado (recomendado)
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", mux)

Razões:

  1. DefaultServeMux é global — qualquer pacote pode registrar rotas, o que pode causar conflitos
  2. Mux personalizado tem escopo controlável, evitando substituições inesperadas de rotas
  3. Mais fácil de reutilizar e testar em diferentes instâncias de servidor

📖 Resumo

Esta lição cobriu o conteúdo central de programação HTTP em Go:

Ponto de Conhecimento Conclusões Principais
Cliente HTTP http.Get/Post para enviar requisições, resp.Body deve ser fechado
Servidor HTTP http.ListenAndServe para iniciar serviço, registro de rotas
Interface Handler Método ServeHTTP(ResponseWriter, *Request)
HandlerFunc Adaptador de função, converte função em Handler
ServeMux Roteador, suporta correspondência de prefixo
Middleware Envolvimento do Handler, insere lógica comum antes/depois das requisições
Desligamento Gracioso Usar server.Shutdown e tratamento de sinais

Padrão Central:

TEXT
Requisição do Cliente → Cadeia de Middleware → Correspondência de Rota → Manipulação do Handler → Retorno da Resposta

📝 Exercícios

Exercício 1: Implementar Serviço de Upload de Arquivo

Requisitos:

Exercício 2: Implementar Middleware de Autenticação JWT

Requisitos:

Exercício 3: Construir um Limitador de Taxa de API Concorrentemente Seguro

Requisitos:


📚 Próxima Lição

Parabéns por completar a programação HTTP! Na próxima lição, aprenderemos a Programação de Testes em Go — incluindo testes unitários, testes orientados por tabela, testes de benchmark e mais, ajudando você a escrever código mais confiável.

Próxima Lição: Testes →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%