Go: Serviços HTTP do Go
Última atualização: 2026-08-26
O pacote
net/httpda biblioteca padrão do Go é completo, permitindo que você desenvolva serviços web de nível de produção sem a necessidade de frameworks de terceiros.
Quando sua equipe decide “usar apenas as bibliotecas padrão, sem introduzir nenhum framework web”, você consegue escrever rotas e middleware de forma clara, assim como no Gin ou no Echo? Nesta lição, você vai dominar todas as tecnologias essenciais dos serviços HTTP em Go.
1. Você aprenderá
http.ListenAndServeInicia o serviço- A interface
Handlere o adaptadorHandlerFunc ServeMuxRegistro de root- Go 1.22: Roteamento aprimorado: Método + Padrão de caminho + Parâmetros de caminho
- Codificação da resposta JSON
- Análise de parâmetros de solicitação (parâmetros de consulta, parâmetros de formulário, parâmetros de caminho)
- Serviço de arquivos estáticos
2. A história real de um engenheiro de backend
(1) Desafio: Uma API simples — escolhemos o Gin, mas, três meses depois, enfrentamos um obstáculo durante a atualização
Alice faz parte da equipe de back-end e precisa configurar uma API REST para o gerenciamento de usuários:
“Escrevi três rotas usando a estrutura Gin: GET /users, POST /users e GET /users/:id. Mas, três meses depois, o Go 1.22 foi lançado, e a biblioteca padrão passou a oferecer suporte nativo a parâmetros de método e de caminho. Agora, quero remover a dependência do Gin, mas teria que alterar todas as assinaturas dos manipuladores — gin.Context versus http.ResponseWriter. Meu chefe disse: ‘Não vale a pena refatorar centenas de linhas de código só para eliminar uma dependência.’”
A decisão dela na época:
// Gin dependency version (wanted to migrate three months later)
r := gin.Default()
r.GET("/users", listUsers) // gin.Context
r.POST("/users", createUser) // gin.Context
r.GET("/users/:id", getUser) // gin.Context
// Want to migrate to the standard library? All handler signatures must change!
(2) Solução para o Go 1.22: roteamento nativo na biblioteca padrão
// Standard library version (Go 1.22+, no dependencies required)
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{{ID: 1, Name: "Alice"}}
func main() {
mux := http.NewServeMux()
// Go 1.22 enhanced routing: method + path pattern + path parameters
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /users/{id}", getUser)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
// Standard handler signature: http.ResponseWriter + *http.Request
func listUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
u.ID = len(users) + 1
users = append(users, u)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(u)
}
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Path parameter!
// Look up user...
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
}
(3) Desempenho: Gin Style x Biblioteca Padrão (Go 1.22)
| Recurso | Gin (de terceiros) | Biblioteca Padrão (Go < 1.22) | Biblioteca Padrão (Go 1.22+) |
|---|---|---|---|
| Parâmetro de caminho | :id ✅ |
❌ Deve ser analisado manualmente | {id} ✅ |
| Roteamento de método | ✅ | ❌ Verificar no manipulador | "GET /path" ✅ |
| Resposta JSON | c.JSON() |
Definir cabeçalho manualmente | Definir cabeçalho manualmente |
| Dependências | 1 pacote externo | 0 | 0 |
| Desempenho | Um pouco mais lento (reflexão) | Nativo | Nativo |
net/http do Go 1.22 é suficiente para a maioria dos projetos web. Se você não precisar de recursos específicos de frameworks (como vinculação/validação automática ou um ecossistema rico de middleware), dê prioridade à biblioteca padrão.
3. Noções básicas sobre HTTP
(1) ▶ Exemplo: O serviço HTTP mais simples
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}
func main() {
http.HandleFunc("/hello", helloHandler)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Teste:
$ curl "http://localhost:8080/hello?name=Alice"
Hello, Alice!
(2) Tipos de núcleo
| Tipo | Descrição |
|---|---|
http.ResponseWriter |
Interface para escrever respostas HTTP |
*http.Request |
Solicitação HTTP, incluindo URL, cabeçalhos, corpo e formulário |
http.Handler |
Interface: ServeHTTP(w, r) |
http.HandlerFunc |
Adaptador de função: converte uma função comum em um Handler |
http.ServeMux |
Multiplexador de rota |
(3) Uma explicação detalhada da interface do manipulador
package main
import (
"fmt"
"log"
"net/http"
)
// Method 1: Implement the Handler interface
type Greeter struct {
Greeting string
}
func (g *Greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s, %s!", g.Greeting, r.URL.Path[1:])
}
// Method 2: HandlerFunc adapter
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}
func main() {
mux := http.NewServeMux()
// Struct Handler
mux.Handle("/greet", &Greeter{Greeting: "Welcome"})
// Function Handler (HandlerFunc automatic conversion)
mux.HandleFunc("/hello", helloHandler)
log.Print(http.ListenAndServe(":8080", mux))
}
4. Go 1.22: Roteamento aprimorado
(1) ▶ Exemplo: Método + Padrão de caminho + Parâmetro de caminho
package main
import (
"encoding/json"
"log"
"net/http"
)
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
var items = []Item{
{ID: 1, Name: "Laptop", Price: 999.99},
{ID: 2, Name: "Mouse", Price: 29.99},
}
func main() {
mux := http.NewServeMux()
// Go 1.22 method + path pattern
mux.HandleFunc("GET /items", listItems)
mux.HandleFunc("POST /items", createItem)
mux.HandleFunc("GET /items/{id}", getItem)
mux.HandleFunc("PUT /items/{id}", updateItem)
mux.HandleFunc("DELETE /items/{id}", deleteItem)
// Wildcard suffix: path prefix matching
mux.HandleFunc("GET /items/{path...}", wildcardHandler)
log.Print(http.ListenAndServe(":8080", mux))
}
func listItems(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items)
}
func createItem(w http.ResponseWriter, r *http.Request) {
var item Item
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
item.ID = len(items) + 1
items = append(items, item)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(item)
}
func getItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// Look up item...
_ = id
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items[0])
}
func updateItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_ = id
// Update logic...
w.WriteHeader(http.StatusNoContent)
}
func deleteItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_ = id
// Delete logic...
w.WriteHeader(http.StatusNoContent)
}
func wildcardHandler(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path")
w.Header().Set("Content-Type", "text/plain")
http.Error(w, "Not found: "+path, http.StatusNotFound)
}
(2) Comparação dos modos de roteamento no Go 1.22
| Modo | Go < 1.22 | Go 1.22+ | Exemplo |
|---|---|---|---|
| Correspondência de métodos | Não suportado (instrução if dentro do Handler) | Suportado | "GET /items" |
| Parâmetro de caminho | Não suportado | {name} Sintaxe |
"GET /items/{id}" |
| Sufixo curinga | Não suportado | {path...} |
"GET /static/{file...}" |
| Caminho exato | /items |
"GET /items" |
Correspondência exata para /items |
| Correspondência de prefixo | /items/ |
"GET /items/" |
Corresponde a /items/... |
(3) ▶ Exemplo: Prioridade de rota
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Exact path > prefix path
mux.HandleFunc("GET /items", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "items list")
})
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "item %s\n", r.PathValue("id"))
})
mux.HandleFunc("GET /items/featured", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "featured items")
})
log.Print(http.ListenAndServe(":8080", mux))
}
Teste:
$ curl localhost:8080/item
item list
$ curl localhost:8080/item/42
item 42
$ curl localhost:8080/item/featured
featured item # Exact match takes priority over {id} wildcard
5. Solicitações e respostas
(1) ▶ Exemplo: Parâmetros de consulta, formulários, JSON
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Response struct {
Method string `json:"method"`
Path string `json:"path"`
Query interface{} `json:"query,omitempty"`
Form interface{} `json:"form,omitempty"`
JSON interface{} `json:"json,omitempty"`
}
func handler(w http.ResponseWriter, r *http.Request) {
resp := Response{
Method: r.Method,
Path: r.URL.Path,
}
// Query parameters
if r.Method == http.MethodGet {
resp.Query = r.URL.Query()
}
// Form data
if r.Method == http.MethodPost {
contentType := r.Header.Get("Content-Type")
switch {
case contentType == "application/x-www-form-urlencoded":
r.ParseForm()
resp.Form = r.Form
case contentType == "application/json":
var body interface{}
json.NewDecoder(r.Body).Decode(&body)
resp.JSON = body
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
log.Print(http.ListenAndServe(":8080", mux))
}
(2) ▶ Exemplo: Função utilitária de resposta JSON
package main
import (
"encoding/json"
"log"
"net/http"
)
// JSON response utility function
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func getProduct(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// Simulate lookup
if id != "1" {
writeError(w, http.StatusNotFound, "product not found")
return
}
writeJSON(w, http.StatusOK, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /products/{id}", getProduct)
log.Print(http.ListenAndServe(":8080", mux))
}
sequenceDiagram
participant Client as HTTP Client
participant Mux as ServeMux
participant Handler as Handler
Client->>Mux: GET /products/1
Mux->>Mux: Route matching
Mux->>Handler: ServeHTTP(w, r)
Handler->>Handler: r.PathValue("id") → "1"
Handler->>Handler: writeJSON(w, 200, product)
Handler-->>Client: HTTP 200 + JSON body
(3) Referência rápida aos códigos de status HTTP
| Código | Constante | Finalidade |
|---|---|---|
| 200 | http.StatusOK |
Sucesso |
| 201 | http.StatusCreated |
Recurso criado com sucesso |
| 204 | http.StatusNoContent |
Sucesso, mas sem corpo da resposta |
| 400 | http.StatusBadRequest |
Erro na solicitação do cliente |
| 401 | http.StatusUnauthorized |
Não autorizado |
| 403 | http.StatusForbidden |
Sem permissão |
| 404 | http.StatusNotFound |
Recurso não encontrado |
| 500 | http.StatusInternalServerError |
Erro interno do servidor |
6. Serviço de arquivos estáticos
(1) ▶ Exemplo: Arquivos estáticos
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Static file service: /static/ prefix → ./static/ directory
mux.Handle("GET /static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("./static"))))
// Single file
mux.Handle("GET /favicon.ico", http.FileServer(http.Dir("./static")))
log.Print(http.ListenAndServe(":8080", mux))
}
7. Exemplo completo: API de notas no estilo REST
// notes_api.go
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"sync"
"time"
)
// ---------- Model ----------
type Note struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ---------- Store ----------
type NoteStore struct {
mu sync.RWMutex
notes map[int]Note
nextID int
}
func NewNoteStore() *NoteStore {
return &NoteStore{
notes: make(map[int]Note),
nextID: 1,
}
}
func (s *NoteStore) Create(title, content string) Note {
s.mu.Lock()
defer s.mu.Unlock()
n := Note{
ID: s.nextID,
Title: title,
Content: content,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
s.nextID++
s.notes[n.ID] = n
return n
}
func (s *NoteStore) List() []Note {
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]Note, 0, len(s.notes))
for _, n := range s.notes {
result = append(result, n)
}
return result
}
func (s *NoteStore) Get(id int) (Note, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
n, ok := s.notes[id]
return n, ok
}
func (s *NoteStore) Update(id int, title, content string) (Note, bool) {
s.mu.Lock()
defer s.mu.Unlock()
n, ok := s.notes[id]
if !ok {
return Note{}, false
}
n.Title = title
n.Content = content
n.UpdatedAt = time.Now()
s.notes[id] = n
return n, true
}
func (s *NoteStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.notes[id]
if !ok {
return false
}
delete(s.notes, id)
return true
}
// ---------- API ----------
type NotesAPI struct {
store *NoteStore
}
func NewNotesAPI(store *NoteStore) *NotesAPI {
return &NotesAPI{store: store}
}
func (api *NotesAPI) Register(mux *http.ServeMux) {
mux.HandleFunc("GET /notes", api.ListNotes)
mux.HandleFunc("POST /notes", api.CreateNote)
mux.HandleFunc("GET /notes/{id}", api.GetNote)
mux.HandleFunc("PUT /notes/{id}", api.UpdateNote)
mux.HandleFunc("DELETE /notes/{id}", api.DeleteNote)
}
// Utility functions
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
// ---------- Handlers ----------
func (api *NotesAPI) ListNotes(w http.ResponseWriter, r *http.Request) {
notes := api.store.List()
writeJSON(w, http.StatusOK, notes)
}
func (api *NotesAPI) CreateNote(w http.ResponseWriter, r *http.Request) {
var input struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
if input.Title == "" {
writeError(w, http.StatusBadRequest, "title is required")
return
}
note := api.store.Create(input.Title, input.Content)
writeJSON(w, http.StatusCreated, note)
}
func (api *NotesAPI) GetNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
note, ok := api.store.Get(id)
if !ok {
writeError(w, http.StatusNotFound, "note not found")
return
}
writeJSON(w, http.StatusOK, note)
}
func (api *NotesAPI) UpdateNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
var input struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
note, ok := api.store.Update(id, input.Title, input.Content)
if !ok {
writeError(w, http.StatusNotFound, "note not found")
return
}
writeJSON(w, http.StatusOK, note)
}
func (api *NotesAPI) DeleteNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
if !api.store.Delete(id) {
writeError(w, http.StatusNotFound, "note not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
func main() {
store := NewNoteStore()
api := NewNotesAPI(store)
mux := http.NewServeMux()
api.Register(mux)
log.Println("Note API started on :8080")
log.Println("Available endpoints:")
log.Println(" GET /notes — List all notes")
log.Println(" POST /notes — Create a note")
log.Println(" GET /notes/{id} — Get a single note")
log.Println(" PUT /notes/{id} — Update a note")
log.Println(" DELETE /notes/{id} — Delete a note")
log.Fatal(http.ListenAndServe(":8080", mux))
}
http.Error(w, msg, code) não define Content-Type como JSON. Se você estiver retornando um erro JSON, use json.NewEncoder(w).Encode(errResp) e defina manualmente Header(). O http.Error() da biblioteca padrão retorna texto simples.
❓ Perguntas Frequentes
P: Como as melhorias de roteamento no Go 1.22 se comparam ao Gin? R: Elas são suficientes para a maioria dos cenários. O Go 1.22 oferece suporte à correspondência de métodos, parâmetros de caminho e curingas. O valor agregado do Gin está na vinculação/validação de solicitações, em seu ecossistema de middleware e no tratamento de erros. Se o seu projeto não exigir esses recursos, a biblioteca padrão é mais leve e mais segura (sem dependências).
P: Qual é a diferença entre
HandlereHandlerFunc? R:Handleré uma interface (que requer a implementação do métodoServeHTTP), enquantoHandlerFuncé um adaptador de tipo de função — ele permite que funções comuns satisfaçam automaticamente a interfaceHandler. Os dois são completamente equivalentes:mux.Handle("/path", handler)emux.HandleFunc("/path", handlerFunc)têm o mesmo efeito.
P: Como faço para recuperar parâmetros de caminho? R: No Go 1.22 e versões posteriores, use
r.PathValue("name")para recuperar parâmetros de caminho do tipo{name}. Em versões anteriores, é necessário analisá-los manualmente a partir der.URL.Pathou usar uma biblioteca de terceiros. Os nomes dos parâmetros de caminho devem corresponder aos{name}no padrão de rota.
P: Qual é a diferença entre
ListenAndServeeListenAndServeTLS? R: O primeiro usa HTTP (porta 80), enquanto o segundo usa HTTPS (porta 443) e requer um certificado e um arquivo de chave privada. Exemplo de HTTPS:http.ListenAndServeTLS(":443", "cert.pem", "key.pem", mux).
P: Como faço para desligar um servidor HTTP de maneira controlada? R: Use
server.Shutdown(ctx)em conjunto comos/signalpara detectar sinais de encerramento.Shutdownaguardará até que todas as conexões ativas sejam processadas antes de fechar. Não useserver.Close()— ele encerrará à força as solicitações que estiverem sendo processadas no momento.
P: O que significa passar
nilcomo o segundo 引数 parahttp.ListenAndServe? R: Passarnilindica quehttp.DefaultServeMux(o roteador padrão global) é utilizado. Recomenda-se criar explicitamentehttp.NewServeMux()para evitar interferir no roteamento global — especialmente ao isolar rotas durante os testes.
P: O ServeMux suporta subrotas aninhadas? R: O ServeMux da biblioteca padrão não suporta rotas aninhadas (agrupamento de subrotas). É possível obter um efeito semelhante registrando manualmente as rotas com um prefixo comum ou utilizando uma biblioteca de roteamento de terceiros, como o chi (que é extremamente leve), para implementar o aninhamento.
📖 Resumo
http.ListenAndServeinicia um serviço HTTP- Interface
Handler+ adaptadorHandlerFunc - Go 1.22 Roteamento aprimorado:
"METHOD /path/{param}"Sintaxe r.PathValue("name")recupera o parâmetro de caminho- Resposta JSON: Set
Content-Type+json.NewEncoder - Análise de solicitações: parâmetros de consulta, formulários, corpo JSON
- Serviço de arquivos estáticos:
http.FileServer - Código de status: Use as constantes
http.Status*
📝 Exercícios
-
Exercício básico (Dificuldade ⭐): Crie um serviço HTTP simples e registre três rotas:
GET /timeretorna a hora atual em JSON,GET /healthretorna{"status": "ok"}eGET /versionretorna o número da versão. Use a sintaxe de roteamento aprimorada do Go 1.22. -
Problema avançado (Dificuldade ⭐⭐): Implemente uma API de lista de tarefas. Requisitos: (1) Operações CRUD completas; (2) Use métodos do Go 1.22 e roteamento baseado em caminho; (3) Solicitações e respostas em JSON; (4) Armazenamento em memória (map + proteção por RWMutex); (5) Retorne códigos de status HTTP apropriados.
-
Desafio (Dificuldade: ⭐⭐⭐): Implemente um serviço de encurtamento de URL. Requisitos: (1)
POST /shortenaceita uma URL longa e retorna um código curto (uma sequência aleatória de 6 caracteres); (2)GET /{code}deve realizar um redirecionamento 301 para a URL original; (3) Estatísticas de acesso:GET /stats/{code}deve retornar o número de visitas; (4) Use-racepara verificar a segurança de concorrência; (5) Use um RWMutex para proteger o contador de estatísticas.