Desenvolvimento de servidor Web em Golang - do simples ao complexo

Desenvolvimento de servidor Web em Golang - do simples ao complexo

Há cinco anos comecei desenvolver Gophish, isso proporcionou uma oportunidade de aprender Golang. Percebi que Go é uma linguagem poderosa, complementada por muitas bibliotecas. Go é versátil: em particular, pode ser usado para desenvolver aplicativos do lado do servidor sem problemas.

Este artigo é sobre como escrever um servidor em Go. Vamos começar com coisas simples como “Olá, mundo!” e terminar com um aplicativo com os seguintes recursos:

- Usando Let's Encrypt para HTTPS.
— Trabalhando como roteador API.
— Trabalhando com middleware.
— Processamento de arquivos estáticos.
— Desligamento correto.

A Skillbox recomenda: curso prático "Desenvolvedor Python do zero".

Lembramos: para todos os leitores de "Habr" - um desconto de 10 rublos ao se inscrever em qualquer curso Skillbox usando o código promocional "Habr".

Olá mundo!

Você pode criar um servidor web em Go muito rapidamente. Aqui está um exemplo de uso de um manipulador que retorna o “Olá, mundo!” prometido acima.

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 “Olá, mundo!” (se tudo funcionar corretamente, claro).

Usaremos o manipulador diversas vezes posteriormente, mas primeiro vamos entender como tudo funciona.

net / http

O exemplo usou o pacote net/http, é a principal ferramenta do Go para desenvolver servidores e clientes HTTP. Para entender o código, vamos entender 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 gera uma resposta. Os manipuladores em Go são implementados da seguinte forma:

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

O primeiro exemplo usa a função auxiliar http.HandleFunc. Ele envolve outra função, que por sua vez leva http.ResponseWriter e http.Request para ServeHTTP.

Em outras palavras, os manipuladores em Golang são apresentados em uma única interface, o que oferece muitas opções ao programador. Assim, por exemplo, o middleware é implementado usando um manipulador, onde o ServeHTTP primeiro faz algo e depois chama o método ServeHTTP de outro manipulador.

Conforme mencionado acima, os manipuladores simplesmente geram respostas às solicitações. Mas qual manipulador específico deve ser usado em um determinado momento?

Solicitar roteamento

Para fazer a escolha certa, use um multiplexador HTTP. Em várias bibliotecas ele é chamado de muxer ou roteador, mas são todos a mesma coisa. A função do multiplexador é analisar o caminho da solicitação e selecionar o manipulador apropriado.

Se você precisar de suporte para roteamento complexo, é melhor usar bibliotecas de terceiros. Alguns dos mais avançados - gorila/mux и go-chi/chi, essas bibliotecas permitem implementar o processamento intermediário sem problemas. Com a ajuda deles, você pode configurar o roteamento curinga e executar diversas outras tarefas. Sua vantagem é a compatibilidade com manipuladores HTTP padrão. Como resultado, você pode escrever código simples que pode ser modificado no futuro.

Trabalhar com estruturas complexas em uma situação normal exigirá soluções não padronizadas e isso complica significativamente o uso de manipuladores padrão. Para criar a grande maioria dos aplicativos, uma combinação da biblioteca padrão e um roteador simples será suficiente.

Processamento de consultas

Além disso, precisamos de um componente que “escute” as conexões recebidas e redirecione todas as solicitações para o manipulador correto. http.Server pode facilmente realizar essa tarefa.

A seguir mostra que o servidor é responsável por todas as tarefas relacionadas ao processamento da conexão. Isso, por exemplo, funciona usando o protocolo TLS. Para implementar a chamada http.ListenAndServer, um servidor HTTP padrão é usado.

Agora vamos ver exemplos mais complexos.

Adicionando Let's Encrypt

Por padrão, nosso aplicativo é executado no protocolo HTTP, mas é recomendado usar o protocolo HTTPS. Isso pode ser feito sem problemas no Go. Se você recebeu um certificado e uma chave privada, basta registrar ListenAndServeTLS com o certificado e os arquivos de chave corretos.

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

Você sempre pode fazer melhor.

Let’s Encrypt fornece certificados gratuitos com renovação automática. Para utilizar o serviço, você precisa de um pacote autocert.

A maneira mais fácil de configurá-lo é usar 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 solicitações:

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

Se abrirmos no navegador example.com, receberemos uma resposta HTTPS “Olá, mundo!”

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

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 fácil de implementar suporte HTTPS completo com renovação automática de certificado.

Adicionando rotas personalizadas

O roteador padrão incluído na biblioteca padrão é bom, mas é muito básico. A maioria dos aplicativos exige roteamento mais complexo, incluindo rotas aninhadas e curinga, ou um procedimento para definir padrões e parâmetros de caminho.

Neste caso vale a pena usar pacotes gorila/mux и go-chi/chi. Aprenderemos como trabalhar com este último - um exemplo é mostrado abaixo.

É fornecido o arquivo api/v1/api.go contendo 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
}

Definimos o prefixo api/vq para rotas no arquivo principal.

Podemos então montá-lo em nosso roteador principal sob o prefixo api/v1/ em nosso aplicativo 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 do Go em trabalhar com rotas complexas torna possível simplificar a estruturação e manutenção de aplicações grandes e complexas.

Trabalhando com middleware

A preparação envolve agrupar um manipulador HTTP com outro, possibilitando executar rapidamente autenticação, compactação, registro e diversas outras funções.

Como exemplo, vejamos a interface http.Handler; vamos usá-la para escrever um manipulador que autentica os 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
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

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

Trabalhando com arquivos estáticos

A biblioteca padrão Go inclui recursos para trabalhar com conteúdo estático, incluindo imagens, JavaScript e arquivos CSS. Eles podem ser acessados ​​através da função http.FileServer. Ele retorna um manipulador que serve arquivos de um diretório específico.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Definitivamente, vale lembrar que http.Dir exibe o conteúdo do diretório se ele não contiver o arquivo index.html principal. Neste caso, para evitar que o diretório seja comprometido, você deve usar o pacote unindexed.

Desligamento correto

Go também possui um recurso chamado desligamento normal do servidor HTTP. Isso pode ser feito usando o método Shutdown(). O servidor é iniciado em uma goroutine e então o canal é escutado para receber um sinal de interrupção. Assim que o sinal é recebido, o servidor desliga, mas não imediatamente, mas após alguns segundos.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Como conclusão

Go é uma linguagem poderosa com uma biblioteca padrão quase universal. Seus recursos padrão são muito amplos e podem ser aprimorados usando interfaces - isso permite desenvolver servidores HTTP verdadeiramente confiáveis.

A Skillbox recomenda:

Fonte: habr.com

Adicionar um comentário