Desenvolvimento de servidores web em Golang — do simples ao complexo

Desenvolvimento de servidores web em Golang — do simples ao complexo

Cinco anos atrás, eu comecei a desenvolver Gophish, isso me deu a oportunidade de aprender Golang. Eu percebi que Go é uma linguagem poderosa, cujas capacidades são complementadas por inúmeras bibliotecas. Go é versátil: em particular, é possível desenvolver aplicativos de servidor sem problemas.

Este artigo é dedicado à escrita de um servidor em Go. Começaremos com coisas simples, como "Hello world!", e terminaremos com um aplicativo que possui as seguintes funcionalidades:

— Uso do Let’s Encrypt para HTTPS.
— Funcionamento como um roteador API.
— Trabalhar com middleware.
— Manipulação de arquivos estáticos.
— Desligamento ordenado.

A Skillbox recomenda: Curso Prático "Desenvolvedor Python do zero".

Lembramos: para todos os leitores do «Habr» — desconto de 10.000 rublos na inscrição em qualquer curso Skillbox com o código promocional «Habr».

Hello, world!

Criar um servidor web em Go pode ser feito muito rapidamente. Aqui está um exemplo de uso de um manipulador que retorna o prometido "Hello, world!".

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

Depois disso, se você executar o aplicativo e abrir a página localhost, você verá imediatamente o texto "Hello, world!" (claro, se tudo estiver funcionando corretamente).

Em seguida, utilizaremos o manipulador repetidamente, mas primeiro vamos entender como tudo funciona.

net/http

No exemplo, foi utilizado o pacote net/http, que é a principal ferramenta em Go para desenvolver tanto servidores quanto clientes HTTP. Para entender o código, vamos analisar o significado de três elementos importantes: http.Handler, http.ServeMux e http.Server.

Manipuladores HTTP

Quando recebemos uma solicitação, o manipulador a analisa e forma uma resposta. Os manipuladores em Go são implementados da seguinte forma:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

No primeiro exemplo, a função auxiliar http.HandleFunc é usada. Ela envolve outra função que, por sua vez, aceita http.ResponseWriter e http.Request no ServeHTTP.

Em outras palavras, os manipuladores em Golang são representados por uma única interface, o que oferece inúmeras possibilidades para o programador. Por exemplo, o middleware é implementado por meio de um manipulador, onde ServeHTTP primeiro faz algo e depois chama o método ServeHTTP de outro manipulador.

Como mencionado acima, os manipuladores apenas formam respostas para solicitações. Mas qual manipulador exatamente devemos usar em um momento específico?

Roteamento de solicitações

Para fazer a escolha correta, utilize o multiplexador HTTP. Em várias bibliotecas, ele é chamado de muxer ou router, mas tudo isso é a mesma coisa. A função do multiplexador consiste em analisar o caminho da solicitação e selecionar o manipulador apropriado.

Se você precisar de suporte para roteamento complexo, é melhor utilizar bibliotecas de terceiros. Algumas das mais avançadas são gorilla/mux e go-chi/chi, essas bibliotecas permitem implementar o processamento intermediário sem grandes problemas. Com elas, é possível configurar roteamento wildcard e realizar várias outras tarefas. Seu ponto positivo é a compatibilidade com manipuladores HTTP padrão. Como resultado, você pode escrever um código simples com a possibilidade de modificá-lo no futuro.

Trabalhar com frameworks complexos geralmente requer soluções não tão padrão, o que complica significativamente o uso dos manipuladores padrão. Para a criação da grande maioria das aplicações, é suficiente a combinação da biblioteca padrão com um roteador simples.

Processamento de solicitações

Além disso, precisamos de um componente que escute as conexões de entrada e redirecione todas as solicitações para o manipulador correto. Para essa tarefa, o http.Server se sai muito bem.

Abaixo está mostrado que o servidor é responsável por todas as tarefas relacionadas ao processamento de conexões. Isso inclui, por exemplo, a operação pelo protocolo TLS. A chamada http.ListenAndServer utiliza o servidor HTTP padrão.

Agora, vamos considerar exemplos mais complexos.

Adicionando o Let’s Encrypt

Por padrão, nossa aplicação opera pelo protocolo HTTP, mas é recomendável usar o protocolo HTTPS. No Go, isso pode ser feito sem problemas. Se você obteve um certificado e uma chave privada, então basta definir o ListenAndServeTLS com os arquivos de certificado e chave corretos.

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

Sempre há espaço para melhorar.

Let’s Encrypt fornece certificados gratuitos com a possibilidade de atualização automática. Para utilizar o serviço, é necessário o pacote autocert.

A maneira mais simples de configurá-lo é utilizando o método autocert.NewListener em combinação com http.Serve. O método permite obter e atualizar certificados TLS enquanto o servidor HTTP processa as solicitações:

http.Serve(autocert.NewListener("example.com"), nil)

Se abrirmos no navegador example.com, receberemos a resposta HTTPS "Hello, world!".

Se precisar de uma configuração mais detalhada, deve-se usar o gerenciador autocert.Manager. Em seguida, criamos nossa própria instância http.Server (até agora, usamos a padrão) e adicionamos o gerenciador na configuração TLS do servidor:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

Esta é uma maneira simples de implementar suporte completo a HTTPS com atualização automática do certificado.

Adicionando rotas não padrão

O roteador padrão incluído na biblioteca padrão é bom, mas é muito simples. A maioria dos aplicativos requer uma roteação mais complexa, incluindo rotas aninhadas e wildcard, ou o procedimento de configuração de templates e parâmetros de caminhos.

Nesse caso, vale a pena usar os pacotes gorilla/mux e go-chi/chi. Com o último, aprenderemos a trabalhar — abaixo está um exemplo.

Dado — o arquivo api/v1/api.go, contendo as rotas para nossa API:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

Configuramos para as rotas o prefixo api/vq no arquivo principal.

Podemos então montar isso no nosso roteador principal sob o prefixo api/v1 de volta em nossa aplicação principal:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

A facilidade de trabalhar com rotas complexas em Go torna possível simplificar a estruturação ao lidar com grandes aplicações complexas.

Trabalhando com middleware

No caso do processamento intermediário, usa-se o encapsulamento de um manipulador HTTP em outro, permitindo rapidamente realizar autenticação, compressão, registro e algumas outras funções.

Como exemplo, considere a interface http.Handler, com a qual escreveremos um manipulador com autenticação de usuários do serviço.

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Supondo que a autenticação tenha sido bem-sucedida, execute o manipulador original
        next.ServeHTTP(w, r)
    })
}

Existem roteadores de terceiros, como o chi, que permitem expandir a funcionalidade do middleware.

Trabalhando com arquivos estáticos

A biblioteca padrão do Go inclui recursos para trabalhar com conteúdo estático, incluindo imagens, bem como arquivos JavaScript e CSS. Você pode acessá-los através da função http.FileServer. Ela retorna um manipulador que serve arquivos de um diretório específico.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Configuração da distribuição de arquivos estáticos
staticPath, _ := filepath.Abs("..//..//static//")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

É importante lembrar que http.Dir exibe o conteúdo do diretório caso não exista um arquivo principal index.html. Neste caso, para evitar a exposição do diretório, você deve usar o pacote não indexado.

Encerramento correto

Em Go, também existe uma função para o encerramento correto do servidor HTTP. Isso pode ser feito usando o método Shutdown(). O servidor é iniciado em uma goroutine, e depois um canal é escutado para receber o sinal de interrupção. Assim que o sinal é recebido, o servidor é desligado, mas não imediatamente, e sim após alguns segundos.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Aguarde uma interrupção
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Tente um encerramento gracioso
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defere cancel()
srv.Shutdown(ctx)

Em conclusão

Go é uma linguagem poderosa com uma biblioteca padrão praticamente universal. Suas capacidades padrão são bastante amplas, e podem ser ampliadas através de interfaces — isso permite desenvolver servidores HTTP realmente confiáveis.

A Skillbox recomenda:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster