Un riepilogo della gestione delle richieste in Go

Ultimo aggiornamento: 21 gennaio 2018 Archiviato in: golang tutorial

L’elaborazione delle richieste HTTP con Go riguarda principalmente due cose: SERVEMUX e gestori.

Un ServeMux è essenzialmente un router di richiesta HTTP (o multiplexor). Confronta le richieste in arrivo con un elenco di percorsi URL predefiniti e chiama il gestore associato per il percorso ogni volta che viene trovata una corrispondenza.

I gestori sono responsabili della scrittura di intestazioni e corpi di risposta. Quasi ogni oggetto può essere un gestore, purché soddisfi l’interfacciahttp.Handler. In parole povere, che significa semplicemente che deve avere un ServeHTTP metodo con la seguente firma:

ServeHTTP(http.ResponseWriter, *http.Request)

Go HTTP pacchetto viene fornito con un paio di funzioni per generare gestori comuni, come ad esempio FileServerNotFoundHandler e RedirectHandler. Iniziamo con un esempio semplice ma artificioso:

$ mkdir handler-example$ cd handler-example$ touch main.go

File: main.vai

package mainimport ( "log" "net/http")func main() { mux := http.NewServeMux() rh := http.RedirectHandler("http://example.org", 307) mux.Handle("/foo", rh) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Passiamo rapidamente a questo:

  • Nella funzione main usiamo la funzione http.NewServeMux per creare un ServeMux vuoto.
  • Usiamo quindi la funzionehttp.RedirectHandler per creare un nuovo gestore. Questo gestore 307 reindirizza tutte le richieste ricevute a http://example.org.
  • Successivamente usiamo la funzionemux.Handle per registrarlo con il nostro nuovo ServeMux, quindi funge da gestore per tutte le richieste in arrivo con il percorso URL/foo.
  • Infine creiamo un nuovo server e iniziamo ad ascoltare le richieste in arrivo con la funzionehttp.ListenAndServe, passando nel nostro ServeMux per farlo corrispondere alle richieste.

Vai avanti ed esegui l’applicazione:

$ go run main.goListening...

E visitahttp://localhost:3000/foo nel tuo browser. Dovresti scoprire che la tua richiesta viene reindirizzata con successo.

L’occhio d’aquila di te potrebbe aver notato qualcosa di interessante: La firma per la funzione ListenAndServe èListenAndServe(addr string, handler Handler), ma abbiamo passato un ServeMux come secondo parametro.

Siamo stati in grado di farlo perché il tipo ServeMux ha anche un metodoServeHTTP, il che significa che soddisfa anche l’interfaccia del gestore.

Per me semplifica le cose pensare a un ServeMux come ad un tipo speciale di gestore, che invece di fornire una risposta passa la richiesta a un secondo gestore. Questo non è tanto un salto quanto sembra per la prima volta – concatenare i gestori insieme è abbastanza comune in Go.

i Gestori Personalizzati

creare un gestore personalizzato che risponde con l’ora locale corrente in un dato formato:

type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}

Il codice esatto, qui non è troppo importante.

Tutto ciò che conta davvero è che abbiamo un oggetto (in questo caso è una struttura timeHandler, ma potrebbe essere ugualmente una stringa o una funzione o qualsiasi altra cosa), e abbiamo implementato un metodo con la firma ServeHTTP(http.ResponseWriter, *http.Request) su di esso. E ‘ tutto quello che ci serve per creare un responsabile.

Incorporiamo questo in un esempio concreto:

File: main.vai

package mainimport ( "log" "net/http" "time")type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() th := &timeHandler{format: time.RFC1123} mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Nella funzione main abbiamo inizializzato il timeHandler esattamente nello stesso modo in cui faremmo qualsiasi struttura normale, usando il & simbolo per produrre un puntatore. E poi, come nell’esempio precedente, usiamo la funzionemux.Handle per registrarlo con il nostro ServeMux.

Ora quando eseguiamo l’applicazione, il ServeMux passerà qualsiasi richiesta per/time direttamente al nostrotimeHandler.ServeHTTP metodo.

Vai avanti e provalo: http://localhost:3000/time.

si Noti anche che potremmo riutilizzare facilmente il timeHandler in più percorsi:

func main() { mux := http.NewServeMux() th1123 := &timeHandler{format: time.RFC1123} mux.Handle("/time/rfc1123", th1123) th3339 := &timeHandler{format: time.RFC3339} mux.Handle("/time/rfc3339", th3339) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Funzioni come Gestori di

Per i casi più semplici (come l’esempio di cui sopra) la definizione di nuovi tipi personalizzati e ServeHTTP metodi si sente un po ‘ prolisso. Diamo un’occhiata a un approccio alternativo, in cui sfruttiamo il tipo http.HandlerFunc di Go per costringere una funzione normale a soddisfare l’interfaccia del gestore.

Qualsiasi funzione che ha la firmafunc(http.ResponseWriter, *http.Request) può essere convertita in un tipo HandlerFunc. Ciò è utile perché gli oggetti HandleFunc sono dotati di un metodo ServeHTTP integrato che, in modo piuttosto intelligente e conveniente, esegue il contenuto della funzione originale.

Se sembra confuso, prova a dare un’occhiata al codice sorgente pertinente. Vedrai che è un modo molto succinto di fare in modo che una funzione soddisfi l’interfaccia del gestore.

Riproduciamo l’applicazione timeHandler utilizzando questa tecnica:

File: principale.go

package mainimport ( "log" "net/http" "time")func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

In effetti, convertire una funzione in un tipo HandlerFunc e quindi aggiungerla a un ServeMux in questo modo è così comune che Go fornisce una scorciatoia: ilmux.HandleFunc metodo.

Questo è ciò che il main() funzione avrebbe guardato come se avessimo usato questo collegamento invece:

func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

la Maggior parte del tempo, utilizzando una funzione come un gestore di come questo funziona bene. Ma c’è un po ‘ di una limitazione quando le cose iniziano a diventare più complesse.

Probabilmente hai notato che, a differenza del metodo precedente, abbiamo dovuto codificare il formato dell’ora nella funzionetimeHandler. Cosa succede quando vogliamo passare informazioni o variabili da main() a un gestore?

Un approccio pulito consiste nel mettere la nostra logica del gestore in una chiusura e chiudere le variabili che vogliamo usare:

File: main.vai

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

La funzionetimeHandler ha ora un ruolo leggermente diverso. Invece di forzare la funzione in un gestore (come abbiamo fatto in precedenza), ora lo stiamo usando per restituire un gestore. Ci sono due elementi chiave per fare questo lavoro.

Prima creafn, una funzione anonima che accede‐ o chiude sopra – la variabileformat formando una chiusura. Indipendentemente da ciò che facciamo con la chiusura, sarà sempre in grado di accedere alle variabili locali all’ambito in cui è stato creato, il che in questo caso significa che avrà sempre accesso alla variabile format.

In secondo luogo la nostra chiusura ha la firmafunc(http.ResponseWriter, *http.Request). Come ricorderai da prima, questo significa che possiamo convertirlo in un tipo HandlerFunc (in modo che soddisfi l’interfaccia del gestore). La nostra funzionetimeHandler restituisce quindi questa chiusura convertita.

In questo esempio abbiamo appena passato una semplice stringa a un gestore. Ma in un’applicazione del mondo reale è possibile utilizzare questo metodo per passare la connessione al database, la mappa del modello o qualsiasi altro contesto a livello di applicazione. È una buona alternativa all’utilizzo di variabili globali e ha il vantaggio di creare gestori autonomi per i test.

Potresti anche vedere questo stesso pattern scritto come:

func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) })}

O usando una conversione implicita al tipo HandlerFunc al ritorno:

func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) }}

Il DefaultServeMux

Probabilmente hai visto DefaultServeMux menzionato in molti posti, dai più semplici esempi di Hello World al codice sorgente Go.

Mi ci è voluto molto tempo per capire che non è niente di speciale. Il DefaultServeMux è solo un semplice ServeMux come abbiamo già usato, che viene istanziato di default quando viene utilizzato il pacchetto HTTP. Ecco la riga pertinente dalla fonte Go:

var DefaultServeMux = NewServeMux()

In genere non si dovrebbe usare DefaultServeMux perché rappresenta un rischio per la sicurezza.

Poiché DefaultServeMux è memorizzato in una variabile globale, qualsiasi pacchetto è in grado di accedervi e registrare un percorso, inclusi i pacchetti di terze parti importati dall’applicazione. Se uno di questi pacchetti di terze parti è compromesso, potrebbero utilizzare DefaultServeMux per esporre un gestore dannoso al Web.

Quindi, come regola generale, è una buona idea evitare DefaultServeMux e utilizzare invece il proprio ServeMux con ambito locale, come siamo stati finora. Ma se hai deciso di usarlo…

Il pacchetto HTTP fornisce un paio di scorciatoie per lavorare con DefaultServeMux: http.Gestire e http.HandleFunc. Questi fanno esattamente lo stesso delle loro funzioni omonime che abbiamo già esaminato, con la differenza che aggiungono gestori al DefaultServeMux invece di uno che hai creato.

Inoltre, ListenAndServe tornerà a utilizzare DefaultServeMux se non viene fornito alcun altro gestore (ovvero, il secondo parametro è impostato sunil).

Quindi, come passo finale, aggiorniamo la nostra applicazione timeHandler per utilizzare invece DefaultServeMux:

File: main.go

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { // Note that we skip creating the ServeMux... var format string = time.RFC1123 th := timeHandler(format) // We use http.Handle instead of mux.Handle... http.Handle("/time", th) log.Println("Listening...") // And pass nil as the handler to ListenAndServe. http.ListenAndServe(":3000", nil)}

Se ti è piaciuto questo post del blog, non dimenticare di dare un’occhiata al mio nuovo libro su come creare applicazioni web professionali con Go!

Seguimi su Twitter @ ajmedwards.

Tutti i frammenti di codice in questo post sono liberi di utilizzare sotto la licenza MIT.



Lascia un commento

Il tuo indirizzo email non sarà pubblicato.