Sviluppo di server Web a Golang: dal semplice al complesso

Sviluppo di server Web a Golang: dal semplice al complesso

Cinque anni fa ho iniziato sviluppare Gophish, questo ha fornito l'opportunità di imparare Golang. Mi sono reso conto che Go è un linguaggio potente, integrato da molte librerie. Go è versatile: in particolare può essere utilizzato per sviluppare applicazioni lato server senza problemi.

Questo articolo riguarda la scrittura di un server in Go. Iniziamo con cose semplici come "Hello world!" e terminiamo con un'applicazione con le seguenti funzionalità:

- Utilizzando Let's Encrypt per HTTPS.
— Funziona come router API.
— Lavorare con il middleware.
— Elaborazione di file statici.
— Spegnimento corretto.

Skillbox consiglia: Corso pratico "Sviluppatore Python da zero".

Ti ricordiamo: per tutti i lettori di "Habr" - uno sconto di 10 rubli al momento dell'iscrizione a qualsiasi corso Skillbox utilizzando il codice promozionale "Habr".

Ciao mondo!

Puoi creare un server web in Go molto rapidamente. Ecco un esempio di utilizzo di un gestore che restituisce il messaggio "Hello, world!" promesso sopra.

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

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

Utilizzeremo il gestore più volte in seguito, ma prima capiamo come funziona il tutto.

rete/http

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

Gestori HTTP

Quando riceviamo una richiesta, il gestore la analizza e genera una risposta. I gestori in Go sono implementati come segue:

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

Il primo esempio utilizza la funzione helper http.HandleFunc. Comprende un'altra funzione, che a sua volta accetta http.ResponseWriter e http.Request in ServeHTTP.

In altre parole, i gestori in Golang sono presentati in un'unica interfaccia, che offre molte opzioni al programmatore. Quindi, ad esempio, il middleware viene implementato utilizzando un gestore, dove ServeHTTP prima fa qualcosa e poi chiama il metodo ServeHTTP di un altro gestore.

Come accennato in precedenza, i gestori generano semplicemente risposte alle richieste. Ma quale particolare gestore dovrebbe essere utilizzato in un particolare momento?

Richiedi instradamento

Per fare la scelta giusta, utilizza un multiplexer HTTP. In molte librerie si chiama muxer o router, ma sono tutte la stessa cosa. La funzione del multiplexer è analizzare il percorso della richiesta e selezionare il gestore appropriato.

Se hai bisogno di supporto per routing complessi, è meglio utilizzare librerie di terze parti. Alcuni dei più avanzati - gorilla/mux и go-chi/chi, queste librerie consentono di implementare l'elaborazione intermedia senza problemi. Con il loro aiuto, puoi configurare il routing con caratteri jolly ed eseguire una serie di altre attività. Il loro vantaggio è la compatibilità con i gestori HTTP standard. Di conseguenza, puoi scrivere codice semplice che può essere modificato in futuro.

Lavorare con framework complessi in una situazione normale richiederà soluzioni non standard e ciò complica notevolmente l'uso dei gestori predefiniti. Per creare la stragrande maggioranza delle applicazioni sarà sufficiente una combinazione della libreria predefinita e di un semplice router.

Elaborazione delle query

Inoltre, abbiamo bisogno di un componente che “ascolti” le connessioni in entrata e reindirizzi tutte le richieste al gestore corretto. http.Server può gestire facilmente questa attività.

Quanto segue mostra che il server è responsabile di tutte le attività correlate all'elaborazione della connessione. Questo, ad esempio, funziona utilizzando il protocollo TLS. Per implementare la chiamata http.ListenAndServer viene utilizzato un server HTTP standard.

Ora diamo un'occhiata ad esempi più complessi.

Aggiunta di Let's Encrypt

Per impostazione predefinita, la nostra applicazione viene eseguita tramite il protocollo HTTP, ma si consiglia di utilizzare il protocollo HTTPS. Questo può essere fatto senza problemi in Go. Se hai ricevuto un certificato e una chiave privata, è sufficiente registrare ListenAndServeTLS con il certificato e i file chiave corretti.

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

Puoi sempre fare meglio.

Let's Encrypt fornisce certificati gratuiti con rinnovo automatico. Per utilizzare il servizio è necessario un pacchetto autocert.

Il modo più semplice per configurarlo è utilizzare il metodo autocert.NewListener in combinazione con http.Serve. Il metodo consente di ottenere e aggiornare i certificati TLS mentre il server HTTP elabora 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, dovresti utilizzare il gestore autocert.Manager. Quindi creiamo la nostra istanza http.Server (fino ad ora l'abbiamo utilizzata per impostazione predefinita) e aggiungiamo il gestore al server 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("", "")

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

Aggiunta di percorsi personalizzati

Il router predefinito incluso nella libreria standard è buono, ma è molto semplice. La maggior parte delle applicazioni richiedono instradamenti più complessi, inclusi instradamenti nidificati e con caratteri jolly, oppure una procedura per impostare modelli e parametri di percorso.

In questo caso vale la pena utilizzare i pacchetti gorilla/mux и go-chi/chi. Impareremo come lavorare con quest'ultimo: un esempio è mostrato di seguito.

Viene fornito il file api/v1/api.go contenente le rotte 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 le rotte nel file principale.

Possiamo quindi montarlo sul nostro router principale con 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 facilità di utilizzo di Go con percorsi complessi consente di semplificare la strutturazione e la manutenzione di applicazioni grandi e complesse.

Lavorare con il middleware

La gestione temporanea implica il confezionamento di un gestore HTTP con un altro, consentendo di eseguire rapidamente l'autenticazione, la compressione, la registrazione e molte altre funzioni.

Ad esempio, diamo un'occhiata all'interfaccia http.Handler; la useremo per scrivere un gestore che autentica gli 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)
    })
}

Esistono router di terze parti, come chi, che consentono di estendere la funzionalità del middleware.

Lavorare con file statici

La libreria standard Go include funzionalità per lavorare con contenuto statico, incluse immagini, file JavaScript e CSS. È possibile accedervi tramite la funzione http.FileServer. Restituisce un gestore che serve file da una directory specifica.

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 sicuramente la pena ricordare che http.Dir visualizza il contenuto della directory se non contiene il file principale index.html. In questo caso, per evitare che la directory venga compromessa, è necessario utilizzare il pacchetto unindexed.

Spegnimento corretto

Go ha anche una funzionalità chiamata arresto regolare del server HTTP. Questo può essere fatto utilizzando il metodo Shutdown(). Il server viene avviato in una goroutine, quindi il canale viene ascoltato per ricevere un segnale di interruzione. Non appena viene ricevuto il segnale, il server si spegne, ma non immediatamente, ma dopo pochi secondi.

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)

In conclusione

Go è un linguaggio potente con una libreria standard quasi universale. Le sue capacità predefinite sono molto ampie e possono essere migliorate utilizzando le interfacce: ciò consente di sviluppare server HTTP veramente affidabili.

Skillbox consiglia:

Fonte: habr.com

Aggiungi un commento