Desenvolvemento de servidores web en Golang - de simple a complexo

Desenvolvemento de servidores web en Golang - de simple a complexo

Hai cinco anos comecei desenvolver Gophish, isto proporcionou a oportunidade de aprender Golang. Decateime de que Go é unha linguaxe poderosa, complementada por moitas bibliotecas. Go é versátil: en particular, pódese usar para desenvolver aplicacións no servidor sen ningún problema.

Este artigo trata sobre escribir un servidor en Go. Comecemos con cousas sinxelas como "Ola mundo!" e rematemos cunha aplicación coas seguintes capacidades:

- Usando Let's Encrypt para HTTPS.
- Traballa como enrutador API.
- Traballar con middleware.
— Procesamento de ficheiros estáticos.
- Apagado correcto.

Skillbox recomenda: Curso práctico "Desenvolvedor Python desde cero".

Recordámolo: para todos os lectores de "Habr" - un desconto de 10 rublos ao inscribirse en calquera curso de Skillbox usando o código promocional "Habr".

Ola, mundo!

Podes crear un servidor web en Go moi rapidamente. Aquí tes un exemplo de uso dun controlador que devolve o "Ola, mundo!" prometido anteriormente.

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)
}

Despois diso, se executa a aplicación e abre a páxina localhost, entón verás inmediatamente o texto "Ola, mundo!" (se todo funciona correctamente, claro).

Usaremos o controlador varias veces máis tarde, pero primeiro imos entender como funciona todo.

net/http

O exemplo utilizou o paquete net/http, é a ferramenta principal de Go para desenvolver servidores e clientes HTTP. Para entender o código, entendamos o significado de tres elementos importantes: http.Handler, http.ServeMux e http.Server.

Manexadores HTTP

Cando recibimos unha solicitude, o manejador analízaa e xera unha resposta. Os controladores en Go impléntanse do seguinte xeito:

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

O primeiro exemplo usa a función auxiliar http.HandleFunc. Envolve outra función, que á súa vez leva http.ResponseWriter e http.Request en ServeHTTP.

Noutras palabras, os controladores en Golang preséntanse nunha única interface, o que dá moitas opcións ao programador. Así, por exemplo, o middleware está implementado mediante un manejador, onde ServeHTTP primeiro fai algo e despois chama ao método ServeHTTP doutro manejador.

Como se mencionou anteriormente, os manejadores simplemente xeran respostas ás solicitudes. Pero que manejador en particular debería usarse nun momento determinado?

Solicitar enrutamento

Para facer a elección correcta, use un multiplexor HTTP. Nunha serie de bibliotecas chámase muxer ou router, pero todas son a mesma cousa. A función do multiplexor é analizar o camiño da solicitude e seleccionar o manejador axeitado.

Se precisa soporte para enrutamento complexo, é mellor usar bibliotecas de terceiros. Algúns dos máis avanzados - gorila/mux и go-chi/chi, estas bibliotecas permiten implementar un procesamento intermedio sen ningún problema. Coa súa axuda, pode configurar o enrutamento comodín e realizar outras tarefas. A súa vantaxe é a compatibilidade cos controladores HTTP estándar. Como resultado, pode escribir código sinxelo que se pode modificar no futuro.

Traballar con marcos complexos nunha situación normal requirirá solucións non estándar, e isto complica significativamente o uso de controladores predeterminados. Para crear a gran maioría das aplicacións, unha combinación da biblioteca predeterminada e un simple router será suficiente.

Procesamento de consultas

Ademais, necesitamos un compoñente que "escoite" as conexións entrantes e redirija todas as solicitudes ao controlador correcto. http.Server pode xestionar esta tarefa facilmente.

O seguinte mostra que o servidor é responsable de todas as tarefas relacionadas co procesamento da conexión. Isto, por exemplo, funciona usando o protocolo TLS. Para implementar a chamada http.ListenAndServer, utilízase un servidor HTTP estándar.

Agora vexamos exemplos máis complexos.

Engadindo Let's Encrypt

Por defecto, a nosa aplicación executa o protocolo HTTP, pero recoméndase utilizar o protocolo HTTPS. Isto pódese facer sen problemas en Go. Se recibiu un certificado e unha clave privada, abonda con rexistrar ListenAndServeTLS co certificado e os ficheiros de clave correctos.

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

Sempre podes facelo mellor.

Imos cifrar ofrece certificados gratuítos con renovación automática. Para utilizar o servizo, necesitas un paquete autocert.

A forma máis sinxela de configuralo é empregar o método autocert.NewListener en combinación con http.Serve. O método permítelle obter e actualizar certificados TLS mentres o servidor HTTP procesa as solicitudes:

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

Se abrimos no navegador example.com, recibiremos unha resposta HTTPS "Ola, mundo!"

Se necesitas unha configuración máis detallada, deberías usar o xestor autocert.Manager. Despois creamos a nosa propia instancia http.Server (ata agora usábaa por defecto) e engadimos o xestor 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("", "")

Este é un xeito sinxelo de implementar compatibilidade completa con HTTPS coa renovación automática do certificado.

Engadindo rutas personalizadas

O enrutador predeterminado incluído na biblioteca estándar é bo, pero é moi básico. A maioría das aplicacións requiren un enrutamento máis complexo, incluíndo rutas aniñadas e comodín, ou un procedemento para establecer patróns e parámetros de ruta.

Neste caso, paga a pena usar paquetes gorila/mux и go-chi/chi. Aprenderemos a traballar con este último; a continuación móstrase un exemplo.

Dáse o ficheiro api/v1/api.go que contén rutas para a nosa 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
}

Establecemos o prefixo api/vq para as rutas no ficheiro principal.

Despois podemos montar isto no noso enrutador principal baixo o prefixo api/v1/ de volta na nosa aplicación 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 Go para traballar con rutas complexas permite simplificar a estruturación e o mantemento de aplicacións grandes e complexas.

Traballando con middleware

A posta en escena implica envolver un controlador HTTP con outro, o que permite realizar rapidamente a autenticación, a compresión, o rexistro e varias outras funcións.

Como exemplo, vexamos a interface http.Handler, empregarémola para escribir un controlador que autentique aos usuarios do servizo.

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)
    })
}

Hai enrutadores de terceiros, como chi, que che permiten ampliar a funcionalidade de middleware.

Traballar con ficheiros estáticos

A biblioteca estándar de Go inclúe capacidades para traballar con contido estático, incluíndo imaxes, ficheiros JavaScript e CSS. Pódese acceder a través da función http.FileServer. Devolve un controlador que serve ficheiros dun directorio 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

Sen dúbida, paga a pena lembrar que http.Dir mostra o contido do directorio se non contén o ficheiro index.html principal. Neste caso, para evitar que o directorio se vexa comprometido, debes usar o paquete unindexed.

Apagado correcto

Go tamén ten unha función chamada apagado gracioso do servidor HTTP. Isto pódese facer usando o método Shutdown(). O servidor iníciase nunha goroutine e, a continuación, escóitase a canle para recibir un sinal de interrupción. Tan pronto como se recibe o sinal, o servidor apágase, pero non inmediatamente, senón despois duns 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 conclusión

Go é unha linguaxe poderosa cunha biblioteca estándar case universal. As súas capacidades predeterminadas son moi amplas e pódense mellorar mediante interfaces; isto permítelle desenvolver servidores HTTP verdadeiramente fiables.

Skillbox recomenda:

Fonte: www.habr.com

Engadir un comentario