O pacote net/http é uma das bibliotecas mais poderosas da linguagem Go, permitindo criar servidores HTTP robustos com poucas linhas de código. Nesta aula, vamos mergulhar nos fundamentos: como registrar rotas, como funciona o roteador padrão (ServeMux), como criar handlers e como iniciar o servidor de forma correta.

Entender esses conceitos é essencial para qualquer aplicação web em Go, desde uma API simples até um sistema complexo com middleware e rotas dinâmicas. Vamos explorar cada parte com exemplos práticos que você pode executar imediatamente.

http.HandleFunc

A função http.HandleFunc é a maneira mais simples de registrar uma função como handler para um padrão de rota. Ela recebe dois argumentos: o padrão (pattern) e a função handler. A função handler deve ter a assinatura func(w http.ResponseWriter, r *http.Request), onde w é usado para escrever a resposta e r contém os dados da requisição.

O padrão pode ser uma rota fixa como "/" ou "/hello", ou pode conter curingas (como "/api/" para capturar tudo que vem depois). O roteador padrão do Go (DefaultServeMux) faz a correspondência de padrões da forma mais longa primeiro.

Aqui está um exemplo simples de uso de http.HandleFunc:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Bem-vindo ao meu servidor!")
    })

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

    http.ListenAndServe(":8080", nil) // nil usa o DefaultServeMux
}

Neste código, definimos duas rotas: a raiz e /hello. Quando o servidor recebe uma requisição para /hello, a função correspondente é chamada. O parâmetro r contém informações como método HTTP, cabeçalhos e corpo, que podem ser usados para lógica adicional.

ServeMux

ServeMux é o roteador HTTP padrão do Go. Ele implementa a interface http.Handler e é responsável por rotear requisições para os handlers registrados. Podemos criar um ServeMux personalizado em vez de usar o DefaultServeMux global, o que é uma boa prática para aplicações maiores, pois permite ter múltiplos roteadores isolados.

Um ServeMux pode ser criado com http.NewServeMux(). Depois, usamos o método HandleFunc para registrar rotas. A vantagem de um mux personalizado é que podemos passá-lo para http.ListenAndServe como segundo argumento, substituindo o mux padrão.

Exemplo:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/about", aboutHandler)

    http.ListenAndServe(":8080", mux)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Página inicial")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Sobre nós")
}

Note que também podemos usar mux.Handle para registrar um handler (um objeto que implementa http.Handler) em vez de uma função. Isso é útil quando temos tipos que implementam o método ServeHTTP.

Handlers

Um handler em Go é qualquer tipo que implemente a interface http.Handler, que exige o método ServeHTTP(w http.ResponseWriter, r *http.Request). Essa interface é central no pacote net/http; tudo que recebe uma requisição e produz uma resposta é um handler.

Além de funções simples, podemos criar structs que implementam ServeHTTP para encapsular lógica mais complexa. Isso é comum em aplicações reais, onde cada rota pode ter seu próprio tipo de handler com dependências (como banco de dados ou serviços).

Exemplo de um handler como struct:

package main

import (
    "fmt"
    "net/http"
)

type apiHandler struct {
    // campos, como um serviço de banco
}

func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "API handler")
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/api", apiHandler{})
    http.ListenAndServe(":8080", mux)
}

Também podemos usar funções como handlers através da conversão http.HandlerFunc, que é um tipo de função que implementa ServeHTTP. Isso é o que acontece por baixo dos panos quando usamos HandleFunc.

Outra técnica importante é o encadeamento de handlers (middleware), onde um handler envolve outro para adicionar funcionalidades como logging ou autenticação. Isso será explorado em aulas futuras.

Iniciando o servidor

Para iniciar um servidor HTTP, usamos a função http.ListenAndServe, que recebe um endereço (como ":8080") e um handler (normalmente um ServeMux). Se passarmos nil, o Go usa o DefaultServeMux. Essa função bloqueia e escuta na porta especificada até ser interrompida.

É importante tratar erros ao iniciar o servidor, pois a porta pode estar em uso ou o endereço pode ser inválido. Podemos usar log.Fatal para exibir o erro e encerrar o programa, ou tratar o erro de forma mais elaborada.

Exemplo com tratamento de erro:

package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Olá, mundo!"))
    })

    log.Println("Servidor rodando em http://localhost:8080")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}

Existe também a função http.Server para configurações mais avançadas, como timeouts e TLS. Isso permite maior controle sobre o servidor, mas para a maioria dos casos, ListenAndServe é suficiente.

Lembre-se de que o servidor roda de forma síncrona; para executar outras tarefas enquanto o servidor está ativo, você pode iniciá-lo em uma goroutine.

Boas práticas e observações finais

Ao desenvolver servidores HTTP em Go, algumas boas práticas incluem: usar um ServeMux personalizado para isolar rotas, evitar o uso do DefaultServeMux em produção, sempre tratar erros de forma adequada, e usar nomes de handlers descritivos. Além disso, é recomendado definir timeouts no servidor para evitar ataques de lentidão.

Outra dica: use o padrão de rota com barra final ("/api/") para capturar sub-rotas, mas lembre-se de que o Go faz redirecionamento automático de "/api" para "/api/" se o padrão terminar com barra. Isso pode causar confusão, então teste bem suas rotas.

Por fim, explore a documentação oficial e os exemplos para aprofundar seu conhecimento.

Referências

Exercícios

  1. Crie um servidor HTTP que responda "Olá, mundo!" na rota /".

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Olá, mundo!")
        })
        http.ListenAndServe(":8080", nil)
    }
    
  2. Registre duas rotas: / e /sobre, cada uma retornando uma mensagem diferente. Use um ServeMux personalizado.

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Página inicial")
        })
        mux.HandleFunc("/sobre", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Sobre nós")
        })
        http.ListenAndServe(":8080", mux)
    }
    
  3. Crie um handler do tipo struct que implemente http.Handler e registre-o na rota /api.

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    type apiHandler struct{}
    
    func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "API handler")
    }
    
    func main() {
        mux := http.NewServeMux()
        mux.Handle("/api", apiHandler{})
        http.ListenAndServe(":8080", mux)
    }
    
  4. Inicie um servidor na porta 9090 e trate possíveis erros usando log.Fatal.

    ✓ Resposta:
    package main
    
    import (
        "log"
        "net/http"
    )
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("Servidor na porta 9090"))
        })
        if err := http.ListenAndServe(":9090", mux); err != nil {
            log.Fatal(err)
        }
    }
    
  5. Use a função http.Server para criar um servidor com configuração de timeout de leitura de 5 segundos e timeout de escrita de 10 segundos.

    ✓ Resposta:
    package main
    
    import (
        "net/http"
        "time"
    )
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("Servidor com timeouts"))
        })
    
        server := &http.Server{
            Addr:         ":8080",
            Handler:      mux,
            ReadTimeout:  5 * time.Second,
            WriteTimeout: 10 * time.Second,
        }
    
        if err := server.ListenAndServe(); err != nil {
            panic(err)
        }
    }