Nesta aula, vamos explorar o pacote net/http da linguagem Go, que fornece todas as ferramentas necessárias para criar servidores HTTP. Você aprenderá a registrar rotas, manipular requisições e respostas, e iniciar um servidor web de forma simples e eficiente. Ao final, você terá uma base sólida para construir aplicações web em Go.

O Go é conhecido por sua simplicidade e desempenho, e o pacote net/http não é exceção. Ele oferece uma API limpa e idiomática, permitindo que você monte servidores HTTP com poucas linhas de código. Vamos começar com os conceitos básicos e avançar para exemplos mais complexos.

http.HandleFunc

A função http.HandleFunc é uma das maneiras mais diretas de registrar uma rota em um servidor HTTP. Ela associa um padrão de URL a uma função handler, que será chamada sempre que uma requisição corresponder a esse padrão.

A assinatura da função é: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)). O primeiro argumento é o padrão de URL (ex: "/", "/hello"), e o segundo é uma função que recebe um http.ResponseWriter e um ponteiro para http.Request. Essa função é responsável por processar a requisição e escrever a resposta.

Exemplo básico:

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá, mundo!")
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    http.ListenAndServe(":8080", nil)
}

Neste exemplo, qualquer requisição para /hello chamará a função helloHandler, que escreve "Olá, mundo!" na resposta. Note que http.HandleFunc registra a rota no DefaultServeMux (o mux padrão), sobre o qual falaremos a seguir.

ServeMux

O ServeMux é um roteador de requisições HTTP. Ele implementa a interface http.Handler e é responsável por encaminhar cada requisição para o handler registrado para o padrão de URL correspondente. O pacote net/http fornece um DefaultServeMux que é usado por padrão, mas você pode criar seus próprios muxes para ter mais controle.

Um ServeMux pode ser criado com http.NewServeMux(). Em seguida, você registra rotas usando o método HandleFunc ou Handle. A vantagem de usar um mux personalizado é que você pode passá-lo explicitamente para http.ListenAndServe, em vez de usar o mux global.

Exemplo com mux personalizado:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Página inicial")
    })
    mux.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Sobre nós")
    })
    http.ListenAndServe(":8080", mux)
}

O ServeMux também suporta padrões mais complexos, como caminhos com curingas (a partir do Go 1.22) e métodos HTTP específicos. Por exemplo, você pode registrar "GET /api/users" para responder apenas a requisições GET. Para mais detalhes, consulte a documentação oficial.

Handlers

Um Handler é uma interface que define o método ServeHTTP(ResponseWriter, *Request). Qualquer tipo que implemente esse método pode ser usado como um handler. O ServeMux é um exemplo de handler, pois implementa ServeHTTP e delega para o handler correto.

Você pode definir seus próprios handlers como structs ou funções. Além disso, o pacote net/http fornece funções utilitárias como http.HandlerFunc, que adapta uma função comum para a interface Handler. Isso é conveniente para criar handlers inline.

Exemplo de handler como struct:

package main

import (
    "fmt"
    "net/http"
)

type messageHandler struct {
    message string
}

func (m messageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, m.message)
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/hello", messageHandler{"Olá, mundo!"})
    mux.Handle("/goodbye", messageHandler{"Adeus!"})
    http.ListenAndServe(":8080", mux)
}

Perceba que usamos mux.Handle em vez de HandleFunc, pois estamos passando um objeto que implementa a interface Handler. Essa abordagem é útil quando você precisa manter estado dentro do handler.

Iniciando o servidor

Para iniciar um servidor HTTP, utilizamos a função http.ListenAndServe. Ela recebe o endereço (host e porta) e um handler (ou nil para usar o DefaultServeMux). A função bloqueia e atende requisições até que o processo seja encerrado.

Exemplo simples:

http.ListenAndServe(":8080", nil)

Você também pode usar http.Server para ter mais controle sobre o servidor, como configurar timeouts e TLS. O tipo http.Server permite configurar propriedades como Addr, Handler, ReadTimeout, WriteTimeout, entre outros.

Exemplo com http.Server:

srv := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  10 * time.Second,
    WriteTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe())

Neste exemplo, criamos um servidor com timeouts de leitura e escrita de 10 segundos. Isso é importante para evitar que conexões lentas prendam recursos indefinidamente. Sempre use log.Fatal para capturar erros ao iniciar o servidor.

Boas Práticas

  • Use um ServeMux personalizado em vez do global para evitar conflitos e facilitar testes.
  • Defina timeouts no servidor para proteger contra ataques de lentidão.
  • Estruture seus handlers em arquivos separados, agrupando por funcionalidade.
  • Utilize http.Error para enviar respostas de erro adequadas.
  • Evite usar o padrão "/" para capturar todas as rotas, a menos que seja necessário.

Referências

Exercícios

  1. Crie um servidor HTTP que responda "Olá, Go!" na rota / e "Bem-vindo!" na rota /welcome. Use o DefaultServeMux.

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Olá, Go!")
        })
        http.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Bem-vindo!")
        })
        http.ListenAndServe(":8080", nil)
    }
  2. Refatore o exercício anterior para usar 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.Fprintf(w, "Olá, Go!")
        })
        mux.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Bem-vindo!")
        })
        http.ListenAndServe(":8080", mux)
    }
  3. Crie um handler do tipo struct que tenha um campo mensagem e implemente ServeHTTP para escrever essa mensagem. Registre-o em duas rotas diferentes.

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    type messageHandler struct {
        message string
    }
    
    func (m messageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, m.message)
    }
    
    func main() {
        mux := http.NewServeMux()
        mux.Handle("/msg1", messageHandler{"Primeira mensagem"})
        mux.Handle("/msg2", messageHandler{"Segunda mensagem"})
        http.ListenAndServe(":8080", mux)
    }
  4. Inicie um servidor usando http.Server com timeouts de leitura e escrita de 5 segundos e um mux personalizado.

    ✓ Resposta:
    package main
    
    import (
        "fmt"
        "net/http"
        "time"
    )
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Servidor com timeouts")
        })
    
        srv := &http.Server{
            Addr:         ":8080",
            Handler:      mux,
            ReadTimeout:  5 * time.Second,
            WriteTimeout: 5 * time.Second,
        }
        log.Fatal(srv.ListenAndServe())
    }
  5. Explique a diferença entre http.HandleFunc e http.Handle. Em que situações você usaria cada um?

    ✓ Resposta:

    http.HandleFunc é uma conveniência que adapta uma função comum para a interface Handler, enquanto http.Handle espera um valor que implemente Handler diretamente. Use HandleFunc quando você tem uma função simples e não precisa de estado; use Handle quando você tem um tipo personalizado (struct) que implementa ServeHTTP e precisa de estado ou lógica complexa.