
Cinque anni fa ho iniziato , questo mi ha dato l'opportunità di studiare Golang. Ho capito che Go è un linguaggio potente, le cui capacità sono ampliate da molte librerie. Go è versatile: in particolare, con esso è possibile sviluppare applicazioni server senza problemi.
Questo articolo è dedicato alla scrittura di un server in Go. Iniziamo con cose semplici, come "Hello world!", e concludiamo con un'applicazione con le seguenti caratteristiche:
— Utilizzo di Let's Encrypt per HTTPS.
— Funzionamento come router API.
— Lavorare con middleware.
— Gestione di file statici.
— Chiusura corretta dell'applicazione.
Skillbox consiglia: Corso pratico .
Ricordiamo: per tutti i lettori di «Habr» — sconto di 10.000 rubli per l'iscrizione a qualsiasi corso Skillbox con il codice promozionale «Habr».
Hello, world!
Creare un server web in Go è molto veloce. Ecco un esempio di un gestore che restituisce il "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)
}Dopo di che, se avvi la applicazione e apri la pagina , vedrai subito il testo "Hello, world!" (ovviamente, se tutto funziona correttamente).
Successivamente utilizzeremo più volte il gestore, ma prima facciamo chiarezza su come funziona tutto.
net/http
Nell'esempio è stato utilizzato il pacchetto , questo è lo strumento principale in Go per sviluppare sia server che client HTTP. Per comprendere il codice, analizziamo tre elementi importanti: http.Handler, http.ServeMux e http.Server.
I gestori HTTP
Quando riceviamo una richiesta, il gestore la analizza e formula una risposta. I gestori in Go sono implementati nel seguente modo:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}Nel primo esempio viene utilizzata la 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 possibilità per il programmatore. Ad esempio, il middleware è implementato tramite un gestore, dove ServeHTTP prima fa qualcosa e poi chiama il metodo ServeHTTP di un altro gestore.
Come detto sopra, i gestori semplicemente formulano risposte alle richieste. Ma quale gestore utilizzare in un dato momento?
Instradamento delle richieste
Per fare la scelta giusta, utilizza il multiplexing HTTP. In alcune librerie viene chiamato muxer o router, ma si tratta della stessa cosa. La funzione del multiplexore consiste nell'analizzare il percorso della richiesta e selezionare il gestore corrispondente.
Se hai bisogno di supporto per una routing complessa, è meglio utilizzare librerie esterne. Tra le più avanzate ci sono e , queste librerie consentono di implementare il middleware senza particolari problemi. Con esse puoi configurare il routing wildcard e svolgere una serie di 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 una situazione normale richiederà soluzioni non standard, il che rende significativamente più difficile l'uso dei gestori predefiniti. Per creare la stragrande maggioranza delle applicazioni è sufficiente una combinazione della libreria predefinita e di un router semplice.
Gestione delle richieste
Inoltre, abbiamo bisogno di un componente che 'ascolti' le connessioni in entrata e indirizzi tutte le richieste al gestore corretto. Con questa funzione può facilmente far fronte http.Server.
Sotto è mostrato che il server è responsabile di tutte le attività relative alla gestione delle connessioni. Questo include, ad esempio, il funzionamento tramite il protocollo TLS. Per attivare http.ListenAndServe viene utilizzato il server HTTP standard.
Ora consideriamo esempi più complessi.
Aggiungere Let’s Encrypt
Per impostazione predefinita, la nostra applicazione funziona tramite il protocollo HTTP, ma si consiglia di utilizzare il protocollo HTTPS. In Go questo è possibile senza problemi. Se hai ottenuto un certificato e una chiave privata, è sufficiente specificare ListenAndServeTLS indicando i corretti file del certificato e della chiave.
http.ListenAndServeTLS(':443', 'cert.pem', 'key.pem', nil)Si può sempre fare meglio.
fornisce certificati gratuiti con possibilità di aggiornamento automatico. Per utilizzare questo servizio, è necessario il pacchetto autocert.
Il modo più semplice per configurarlo è usare 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 , otterremo la risposta HTTPS «Hello, world!».
Se è necessaria una configurazione più dettagliata, è consigliabile utilizzare il gestore autocert.Manager. Quindi creiamo una nostra istanza di http.Server (fino a questo momento abbiamo utilizzato quella predefinita) e aggiungiamo il gestore alla configurazione 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 pieno supporto HTTPS con aggiornamento automatico del certificato.
Aggiunta di rotte personalizzate
Il router predefinito incluso nella libreria standard è buono, ma è molto semplice. Nella maggior parte delle applicazioni è necessaria una routing più complesso, incluse rotte nidificate e wildcard, o anche una procedura per impostare modelli e parametri delle strade.
In questo caso, è opportuno utilizzare i pacchetti e . Con quest'ultimo impareremo a lavorare — di seguito è mostrato un esempio.
Dato — 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 delle rotte api/v1 nel file principale.
Possiamo quindi montare questo 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à di lavorare con rotte complesse in Go rende possibile semplificare la strutturazione della manutenzione di grandi applicazioni complesse.
Lavorare con middleware
Nel caso del middleware, si utilizza l'incapsulamento di un gestore HTTP all'interno di un altro, il che consente di effettuare rapidamente autenticazione, compressione, logging e alcune altre funzionalità.
Come esempio, consideriamo l'interfaccia http.Handler, con essa scriveremo un gestore per l'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
}
\/\/ Assumendo che l'autenticazione sia passata, esegui il gestore originale
next.ServeHTTP(w, r)
})
}Ci sono router di terze parti, come chi, che consentono di estendere le funzionalità del middleware.
Lavorare con file statici
La libreria standard di Go include funzioni per lavorare con contenuti statici, comprese immagini, e file JavaScript e CSS. È possibile accedervi attraverso la funzione http.FileServer. Questa restituisce un gestore che distribuisce file da una directory specificata.
func NewRouter() http.Handler {
router := chi.NewRouter()
r.Get("/{name}", HelloName)
// Configurazione della distribuzione dei 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 il file principale index.html. In questo caso, per evitare la compromissione della directory, è opportuno utilizzare il pacchetto .
Chiusura corretta
In Go esiste anche una funzione per la chiusura corretta del server HTTP. Questo può essere fatto utilizzando il metodo Shutdown(). Il server viene avviato in una goroutine e successivamente un 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...))
}()
// Attendere un'interruzione
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Tentativo di chiusura elegante
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à predefinite sono ampie e possono essere ampliate grazie alle interfacce, il che consente di sviluppare server HTTP realmente robusti.
Skillbox consiglia:
- Corso pratico di due anni .
- Corso online educativo .
- Corso pratico annuale .
Fonte: habr.com
