Desenvolvimento de servidor Web Golang - do fácil ao complexo



Há cinco anos, comecei a desenvolver Gophish , o que tornou possível aprender Golang. Percebi que o Go é uma linguagem poderosa, cujos recursos são complementados por muitas bibliotecas. O Go é universal: em particular, com sua ajuda, é possível desenvolver aplicativos de servidor sem problemas.

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

- Usando o Let's Encrypt for HTTPS.
- Trabalhe como um roteador API.
- Trabalhe com middleware.
- Processamento de arquivos estáticos.
- Desligamento correto.

A Skillbox recomenda: O desenvolvedor Python desde o início do curso prático .

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

Olá mundo!


Criar um servidor Web no Go é muito rápido. 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ê iniciar o aplicativo e abrir a página localhost , verá imediatamente o texto "Olá, mundo!" (é claro, se tudo funcionar corretamente).

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

net / http


O exemplo usou o pacote net/http , que é a principal ferramenta do Go para desenvolver servidores e clientes HTTP. Para entender o código, vejamos o significado de três elementos importantes: http.Handler, http.ServeMux e http.Server.

Manipuladores HTTP


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

 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 aceita http.ResponseWriter e http.Request no ServeHTTP.

Em outras palavras, os manipuladores Golang são representados por uma única interface, que oferece muitas oportunidades para o programador. Assim, por exemplo, o middleware é implementado usando um manipulador, onde o ServeHTTP primeiro faz alguma coisa e depois chama o método ServeHTTP de outro manipulador.

Como 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 o multiplexador HTTP. É chamado muxer ou roteador em várias bibliotecas, mas é tudo 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. Uma das mais avançadas é gorilla / mux e go-chi / chi , essas bibliotecas possibilitam implementar o processamento intermediário sem problemas. Com a ajuda deles, você pode configurar o roteamento de caracteres curinga e executar várias outras tarefas. Sua vantagem é a compatibilidade com os manipuladores HTTP padrão. Como resultado, você pode escrever um código simples com a possibilidade de sua modificação no futuro.

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

Processamento de solicitação


Além disso, precisamos de um componente que "escute" as conexões recebidas e redirecione todas as solicitações para o manipulador correto. Esta tarefa pode ser facilmente executada pelo http.Server.

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

Agora vamos ver exemplos mais complexos.

Adicionando o Let's Encrypt


Por padrão, nosso aplicativo é executado sobre o protocolo HTTP, mas é recomendável usar o protocolo HTTPS. No Go, isso pode ser feito sem problemas. Se você recebeu um certificado e uma chave privada, basta registrar o 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 a capacidade de renovar automaticamente. Para usar o serviço, você precisa do 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 receber e renovar certificados TLS, enquanto o servidor HTTP processa solicitações:

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

Se abrirmos o example.com no navegador, obteremos a resposta HTTPS "Olá, mundo!".

Se você precisar de uma configuração mais completa, use 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("", "") 

Essa é uma maneira fácil de implementar o suporte completo a HTTPS com renovação automática de certificado.

Adicionando rotas personalizadas


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

Nesse caso, você deve usar os pacotes gorilla / mux e go-chi / chi . Vamos aprender a trabalhar com este último - um exemplo é mostrado abaixo.

Dado é o arquivo api / v1 / api.go que contém 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 } 

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

Podemos então montar isso 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 simplicidade de trabalhar com rotas complexas no Go torna possível simplificar a estruturação com o atendimento de grandes aplicativos complexos.

Trabalhar com middleware


No caso do processamento intermediário, é usado o empacotamento de um manipulador HTTP com outro, o que possibilita a autenticação, compactação, log e algumas outras funções rapidamente.

Como exemplo, considere a interface http.Handler, com sua ajuda, escrevemos 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 } // Assuming authentication passed, run the original handler next.ServeHTTP(w, r) }) } 

Existem roteadores de terceiros, por exemplo, chi, que permitem estender a funcionalidade do processamento intermediário.

Trabalhar com arquivos estáticos


A biblioteca padrão Go inclui recursos para trabalhar com conteúdo estático, incluindo imagens, além de arquivos JavaScript e CSS. Eles podem ser acessados ​​através da função http.FileServer. Retorna um manipulador que distribui 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 

Vale lembrar que http.Dir exibe o conteúdo do diretório se ele não possui o arquivo index.html principal. Nesse caso, para impedir que o diretório seja comprometido, vale a pena usar o pacote não unindexed .

Desligamento correto


O Go também possui um recurso como desligar o servidor HTTP corretamente. Isso pode ser feito usando o método Shutdown (). O servidor inicia na goroutine e, em seguida, o canal é ouvido para receber um sinal de interrupção. Assim que o sinal é recebido, o servidor é encerrado, 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) 

Em conclusão


Go é uma linguagem poderosa com uma biblioteca padrão quase universal. Seus recursos padrão são muito amplos e você pode fortalecê-los com a ajuda de interfaces - isso permite que você desenvolva servidores HTTP verdadeiramente confiáveis.
A Skillbox recomenda:

Source: https://habr.com/ru/post/pt446454/


All Articles