Desenvolupament de servidors web a Golang, de simple a complex

Desenvolupament de servidors web a Golang, de simple a complex

Fa cinc anys vaig començar desenvolupar Gophish, va permetre aprendre Golang. Em vaig adonar que Go és un llenguatge potent, que es complementa amb moltes biblioteques. Go és versàtil: en particular, podeu desenvolupar fàcilment aplicacions del costat del servidor amb ell.

Aquest article tracta sobre escriure un servidor a Go. Comencem amb coses senzilles com "Hola món!" i acabem amb una aplicació amb aquestes funcions:

- Ús de Let's Encrypt per a HTTPS.
- Treballar com a router API.
- Treball amb middleware.
- Maneig de fitxers estàtics.
- Apagada correcta.

Skillbox recomana: Curs pràctic "Desenvolupador Python des de zero".

Recordem: per a tots els lectors de "Habr": un descompte de 10 rubles en inscriure's a qualsevol curs de Skillbox amb el codi promocional "Habr".

Hola món!

Crear un servidor web a Go pot ser molt ràpid. Aquí teniu un exemple d'ús d'un controlador que retorna el "Hola, món!" promès anteriorment.

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

Després d'això, si executeu l'aplicació i obriu la pàgina localhost, de seguida veureu el text "Hola, món!" (per descomptat, si tot funciona correctament).

A continuació, utilitzarem el controlador repetidament, però primer anem a entendre com funciona tot.

net/http

L'exemple utilitzava el paquet net/http, és l'eina principal de Go per desenvolupar servidors i clients HTTP. Per entendre el codi, entenem el significat de tres elements importants: http.Handler, http.ServeMux i http.Server.

Controladors HTTP

Quan rebem una sol·licitud, el gestor l'analitza i genera una resposta. Els gestors de Go s'implementen de la següent manera:

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

El primer exemple utilitza la funció d'ajuda http.HandleFunc. Embolcalla una altra funció que al seu torn accepta http.ResponseWriter i http.Request a ServeHTTP.

En altres paraules, els controladors a Golang estan representats per una única interfície, la qual cosa ofereix moltes oportunitats per al programador. Així, per exemple, el middleware s'implementa mitjançant un controlador, on ServeHTTP primer fa alguna cosa i després crida al mètode ServeHTTP d'un altre controlador.

Com s'ha esmentat anteriorment, els gestors simplement formen respostes a les sol·licituds. Però quin gestor s'ha d'utilitzar en un moment concret?

Sol·licitud d'encaminament

Per prendre la decisió correcta, utilitzeu el multiplexor HTTP. En diverses biblioteques s'anomena muxer o router, però totes són iguals. La funció del multiplexor és analitzar el camí de la sol·licitud i seleccionar el controlador adequat.

Si necessiteu suport per a un enrutament complex, és millor utilitzar biblioteques de tercers. Un dels més avançats goril·la/mux и go-chi/chi, aquestes biblioteques permeten implementar processaments intermedis sense cap problema. Amb la seva ajuda, podeu configurar l'encaminament de comodins i realitzar altres tasques. El seu avantatge és la compatibilitat amb els controladors HTTP estàndard. Com a resultat, podeu escriure codi senzill que es pot modificar en el futur.

Treballar amb marcs complexos en una situació normal requerirà solucions no del tot estàndards, i això complica molt l'ús de controladors predeterminats. La combinació de la biblioteca per defecte i un simple encaminador serà suficient per crear la gran majoria d'aplicacions.

Processament de consultes

A més, necessitem un component que "escolti" les connexions entrants i redirigeixi totes les sol·licituds al gestor correcte. http.Server pot fer front fàcilment a aquesta tasca.

A continuació es mostra que el servidor és responsable de totes les tasques relacionades amb la gestió de connexions. Això és, per exemple, treballar amb el protocol TLS. S'utilitza un servidor HTTP estàndard per implementar la trucada http.ListenAndServer.

Vegem ara exemples més complexos.

Afegint Let's Encrypt

Per defecte, la nostra aplicació s'executa sobre el protocol HTTP, però es recomana utilitzar el protocol HTTPS. A Go, això es pot fer sense problemes. Si heu rebut un certificat i una clau privada, n'hi ha prou amb escriure ListenAndServeTLS amb el certificat i els fitxers de clau correctes.

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

Sempre pots fer-ho millor.

Anem a encriptar ofereix certificats gratuïts amb possibilitat de renovació automàtica. Per utilitzar el servei, necessiteu un paquet autocert.

La manera més senzilla de configurar-lo és utilitzar el mètode autocert.NewListener en combinació amb http.Serve. El mètode us permet rebre i renovar certificats TLS mentre el servidor HTTP processa les sol·licituds:

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

Si obrim al navegador example.com, obtenim una resposta HTTPS "Hola, món!".

Si necessiteu una configuració més detallada, haureu d'utilitzar autocert.Manager. Aleshores creem la nostra pròpia instància http.Server (fins ara l'hem utilitzat per defecte) i afegim el gestor al 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("", "")

Aquesta és una manera senzilla d'implementar el suport complet d'HTTPS amb la renovació automàtica del certificat.

Afegir rutes personalitzades

L'encaminador predeterminat inclòs a la biblioteca estàndard és agradable, però molt bàsic. La majoria de les aplicacions necessiten un encaminament més complex, incloses les rutes imbricades i amb comodins, o establir patrons i paràmetres de ruta.

En aquest cas, hauríeu d'utilitzar paquets goril·la/mux и go-chi/chi. Aprendrem a treballar amb aquest últim; a continuació es mostra un exemple.

Es dóna el fitxer api/v1/api.go que conté les rutes per a la nostra 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
}

Establim el prefix api/vq per a les rutes al fitxer principal.

A continuació, podem muntar-ho al nostre encaminador principal amb el prefix api/v1/ de nou a la nostra aplicació 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())

La facilitat de treballar amb rutes complexes a Go permet simplificar l'estructuració i el manteniment de grans aplicacions complexes.

Treballant amb middleware

En el cas del processament intermedi, s'utilitza l'embolcall d'un controlador HTTP amb un altre, cosa que permet realitzar ràpidament l'autenticació, la compressió, el registre i algunes altres funcions.

Com a exemple, considerem la interfície http.Handler, amb la seva ajuda escriurem un controlador amb autenticació dels usuaris del servei.

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

Hi ha encaminadors de tercers, com ara chi, que us permeten ampliar la funcionalitat del processament intermedi.

Treballar amb fitxers estàtics

La biblioteca estàndard de Go inclou instal·lacions per treballar amb contingut estàtic, incloses imatges, així com fitxers JavaScript i CSS. S'hi pot accedir mitjançant la funció http.FileServer. Retorna un controlador que distribueix fitxers des d'un directori específic.

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

Assegureu-vos de recordar que http.Dir mostra el contingut del directori si no conté el fitxer index.html principal. En aquest cas, per evitar que el directori es vegi compromès, hauríeu d'utilitzar el paquet unindexed.

Apagada correcta

Go també té una característica com ara un tancament elegant del servidor HTTP. Això es pot fer mitjançant el mètode Shutdown(). El servidor s'inicia en una goroutine i, a continuació, s'escolta el canal per rebre un senyal d'interrupció. Tan bon punt es rep el senyal, el servidor s'apaga, però no immediatament, sinó després d'uns segons.

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)

Com a conclusió

Go és un llenguatge potent amb una biblioteca estàndard gairebé universal. Les seves capacitats per defecte són molt àmplies i les podeu reforçar amb l'ajuda d'interfícies; això us permet desenvolupar servidors HTTP realment fiables.

Skillbox recomana:

Font: www.habr.com

Afegeix comentari