Sviluppo di server web in Golang — da semplice a complesso

Sviluppo di server web in Golang — da semplice a complesso

Cinque anni fa ho iniziato a sviluppare Gophish, il che mi ha permesso di approfondire Golang. Ho capito che Go è un linguaggio potente, le cui funzionalità sono ampliate da numerose librerie. Go è versatile: in particolare, consente di sviluppare senza problemi applicazioni server.

Questo articolo è dedicato alla scrittura di un server in Go. Iniziamo con cose semplici, come "Hello world!", e finiamo con un'applicazione con le seguenti funzionalità:

— Utilizzo di Let's Encrypt per HTTPS.
— Funziona come router API.
— Gestione di middleware.
— Elaborazione di file statici.
— Chiusura corretta.

Skillbox consiglia: Corso pratico «Sviluppatore Python da zero».

Ricordiamo: per tutti i lettori di «Habr» — sconto di 10.000 rubli per l'iscrizione a qualsiasi corso Skillbox con il codice promozionale «Habr».

Ciao, mondo!

Creare un server web in Go è molto veloce. Ecco un esempio di utilizzo di un gestore che restituisce il promettente "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)
}

Dopo di che, se esegui l'applicazione e apri la pagina localhost, vedrai immediatamente il testo "Hello, world!" (certo, se tutto funziona correttamente).

In seguito utilizzeremo ripetutamente il gestore, ma prima facciamo un po' di chiarezza su come funziona tutto.

net/http

Nell'esempio è stato utilizzato il pacchetto net/http, il principale strumento in Go per sviluppare sia server che client HTTP. Per comprendere il codice, vediamo il significato di tre elementi chiave: http.Handler, http.ServeMux e http.Server.

Gestori HTTP

Quando riceviamo una richiesta, il gestore la analizza e forma una risposta. I gestori in Go sono implementati nel seguente modo:

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

Nel primo esempio si utilizza una funzione di supporto http.HandleFunc. Questa avvolge un'altra funzione che, a sua volta, accetta http.ResponseWriter e http.Request in ServeHTTP.

In altre parole, i gestori in Golang sono rappresentati da un'unica interfaccia, il che offre molte opportunità per il programmatore. Ad esempio, il middleware è implementato tramite un gestore, dove ServeHTTP prima esegue un'azione e poi chiama il metodo ServeHTTP di un altro gestore.

Come già detto, i gestori semplicemente formano risposte alle richieste. Ma quale gestore usare in un dato momento?

Routing delle richieste

Per fare la scelta giusta, utilizza un multiplexor HTTP. In alcune librerie, è chiamato muxer o router, ma sono tutti sinonimi. La funzione del multiplexor consiste nell'analizzare il percorso della richiesta e scegliere il gestore appropriato.

Se hai bisogno di supporto per una routing complessa, è meglio utilizzare librerie di terze parti. Alcune delle più avanzate sono: gorilla/mux e go-chi/chi, queste librerie offrono la possibilità di implementare il middleware senza particolari problemi. Grazie a loro puoi configurare la routing wildcard e svolgere altre attività. Il loro vantaggio è la compatibilità con i gestori HTTP standard. Di conseguenza, puoi scrivere codice semplice con la possibilità di modificarlo in futuro.

Lavorare con framework complessi in situazioni normali richiederà soluzioni non standard, il che complica notevolmente l'utilizzo dei gestori predefiniti. Per la creazione della stragrande maggioranza delle applicazioni, è sufficiente una combinazione della libreria predefinita e di un router non complesso.

Elaborazione delle richieste

Inoltre, abbiamo bisogno di un componente che "ascolti" le connessioni in entrata e reindirizzi tutte le richieste al corretto gestore. Questa attività può essere gestita facilmente da http.Server.

Di seguito, viene mostrato che il server si occupa di tutte le operazioni relative alla gestione delle connessioni. Ad esempio, il lavoro con il protocollo TLS. Per implementare la chiamata http.ListenAndServer si utilizza il server HTTP standard.

Ora vediamo esempi più complessi.

Aggiunta di Let’s Encrypt

Per default, la nostra applicazione funziona con il protocollo HTTP, ma è consigliato utilizzare il protocollo HTTPS. In Go, questo è facilmente realizzabile. Se hai ottenuto il certificato e la chiave privata, devi semplicemente impostare ListenAndServeTLS specificando i file corretti del certificato e della chiave.

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

Si può sempre fare meglio.

Let’s Encrypt offre certificati gratuiti con possibilità di aggiornamento automatico. Per usufruire del servizio, è necessario il pacchetto autocert.

Il modo più semplice per configurarlo è utilizzare il metodo autocert.NewListener in combinazione con http.Serve. Questo metodo consente di ottenere e aggiornare i certificati TLS mentre il server HTTP gestisce le richieste:

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

Se apriamo nel browser example.com, riceveremo una risposta HTTPS "Hello, world!".

Se hai bisogno di una configurazione più dettagliata, puoi utilizzare il gestore autocert.Manager. Quindi creiamo un'istanza personalizzata di http.Server (fino ad ora abbiamo usato l'istanza predefinita) e aggiungiamo il gestore a TLSConfig del server:

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("", "")

Questo è un modo semplice per implementare il supporto completo per HTTPS con aggiornamento automatico del certificato.

Aggiunta di percorsi personalizzati

Il router predefinito incluso nella libreria standard è buono, ma è molto semplice. La maggior parte delle applicazioni richiede una gestione più complessa dei percorsi, incluse le routes annidate e wildcard, o la procedura per impostare modelli e parametri dei percorsi.

In questo caso, è consigliabile utilizzare i pacchetti gorilla/mux e go-chi/chi. Con l'ultimo impareremo a lavorare — di seguito è mostrato un esempio.

Data — file api/v1/api.go contenente i percorsi per 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
}

Impostiamo il prefisso api/vq per i percorsi nel file principale.

Possiamo poi montarlo sul nostro router principale sotto il prefisso api/v1 nella nostra applicazione principale:

// 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 semplicità nel gestire percorsi complessi in Go rende possibile semplificare la strutturazione nella gestione di grandi applicazioni complesse.

Lavorare con middleware

Nel caso del middleware, viene utilizzato l'inserimento di un gestore HTTP all'interno di un altro, il che consente una rapida autenticazione, compressione, registrazione e altre funzioni.

Come esempio, consideriamo l'interfaccia http.Handler e scriviamo un gestore con autenticazione degli utenti del servizio.

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

Ci sono router di terze parti, come chi, che permettono di estendere le funzionalità del middleware.

Gestire file statici

La libreria standard di Go include funzionalità per lavorare con contenuti statici, comprese immagini, file JavaScript e CSS. È possibile accedervi tramite la funzione http.FileServer. Questa restituisce un gestore che serve file da una directory specificata.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Configurazione per la distribuzione di file statici
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

È importante ricordare che http.Dir restituisce il contenuto della directory solo se non è presente un file principale index.html. In questo caso, per evitare la compromissione della directory, è consigliabile utilizzare il pacchetto non indicizzato.

Terminazione corretta

In Go è presente anche una funzione per la terminazione corretta del server HTTP. Questo può essere fatto utilizzando il metodo Shutdown(). Il server viene avviato in una goroutine, quindi il canale viene monitorato per ricevere un segnale di interruzione. Una volta ricevuto il segnale, il server si spegne, ma non immediatamente, bensì dopo alcuni secondi.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Aspetta un'interruzione
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Tentativo di arresto controllato
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

In conclusione

Go — un linguaggio potente con una libreria standard praticamente universale. Le sue funzionalità di default sono molto ampie e possono essere ampliate grazie alle interfacce, il che consente di sviluppare server HTTP veramente affidabili.

Skillbox consiglia:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster