
Cinco anos atrás, eu comecei , 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 .
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 , 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 , 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 e , 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.
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 , 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 e . 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 .
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:
- Curso Prático de Dois Anos .
- Curso online educacional .
- Curso prático de um ano .
Fonte: habr.com
