Een samenvatting van de Aanvraagafhandeling in Go

laatst bijgewerkt: 21 januari 2018 gearchiveerd onder: golang tutorial

het verwerken van HTTP-verzoeken met Go gaat voornamelijk over twee dingen: ServeMuxes en Handlers.

een ServeMux is in wezen een HTTP request router (of multiplexor). Het vergelijkt inkomende aanvragen met een lijst met vooraf gedefinieerde URL-paden en roept de bijbehorende handler voor het pad aan wanneer een overeenkomst wordt gevonden.

Handlers zijn verantwoordelijk voor het schrijven van antwoord headers en body ‘ s. Bijna elk object kan een handler zijn, zolang het voldoet aan de http.Handler interface. In lektermen betekent dat dat het een ServeHTTP methode moet hebben met de volgende handtekening:

ServeHTTP(http.ResponseWriter, *http.Request)

Go ‘ S HTTP-pakket bevat een paar functies om gemeenschappelijke handlers te genereren, zoals FileServerNotFoundHandler en RedirectHandler. Laten we beginnen met een eenvoudig maar gekunsteld voorbeeld:

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

File: main.go

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

laten we hier snel doorheen gaan:

  • In de main functie gebruiken we de http.NewServeMux functie om een lege ServeMux te maken.
  • we gebruiken dan de functie http.RedirectHandler om een nieuwe handler aan te maken. Deze handler 307 stuurt alle verzoeken die het ontvangt door naar http://example.org.
  • vervolgens gebruiken we de mux.Handle functie om dit te registreren met onze nieuwe ServeMux, zodat het fungeert als de handler voor alle inkomende aanvragen met het URL pad /foo.
  • tot slot maken we een nieuwe server en beginnen te luisteren naar inkomende verzoeken met de http.ListenAndServe functie, waarbij we onze ServeMux doorgeven om verzoeken mee te matchen.

voer de toepassing uit:

$ go run main.goListening...

en bezoek http://localhost:3000/foo in uw browser. U moet vinden dat uw verzoek met succes wordt omgeleid.

De eagle-eyed van u heeft misschien iets interessants opgemerkt: de handtekening voor de ListenAndServe functie is ListenAndServe(addr string, handler Handler), maar we hebben een ServeMux als de tweede parameter doorgegeven.

We waren in staat om dit te doen omdat het ServeMux type ook een ServeHTTP methode heeft, wat betekent dat het ook voldoet aan de Handler interface.

voor mij vereenvoudigt het om te denken dat een ServeMux gewoon een speciaal soort handler is, die in plaats van zelf een antwoord te geven het verzoek doorgeeft aan een tweede handler. Dit is niet zo veel van een sprong als het eerste klinkt-chaining handlers samen is vrij alledaags in Go.

aangepaste Handlers

laten we een aangepaste handler maken die reageert met de huidige lokale tijd in een bepaald formaat:

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

de exacte code hier is niet zo belangrijk.

het enige wat er echt toe doet is dat we een object hebben (in dit geval is het een timeHandler struct, maar het kan ook een string of functie of iets anders zijn), en we hebben een methode geïmplementeerd met de handtekening ServeHTTP(http.ResponseWriter, *http.Request) erop. Dat is alles wat we nodig hebben om een begeleider te maken.

laten we dit in een concreet voorbeeld insluiten:

File: main.go

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

in de main functie initialiseerden we de timeHandler op precies dezelfde manier als elke normale struct, met behulp van de & symbool om een aanwijzer op te geven. En dan, net als het vorige voorbeeld, gebruiken we de mux.Handle functie om dit te registreren met onze ServeMux.

als we nu de toepassing draaien, zal de ServeMux elke aanvraag voor /time direct doorgeven aan onze timeHandler.ServeHTTP methode.

ga je gang en probeer het eens: http://localhost:3000/time.

merk ook op dat we de timeHandler op meerdere routes kunnen hergebruiken:

functioneert als handlers

voor eenvoudige gevallen (zoals het voorbeeld hierboven) het definiëren van nieuwe aangepaste types en servehttp methoden voelt een beetje uitgebreid. Laten we eens kijken naar een alternatieve aanpak, waar we gebruik maken van Go ‘ s http.HandlerFunc type om een normale functie te dwingen om te voldoen aan de Handler interface.

elke functie met de handtekening func(http.ResponseWriter, *http.Request) kan worden omgezet in een HandlerFunc type. Dit is handig omdat HandleFunc objecten worden geleverd met een ingebouwde ServeHTTP methode die – vrij slim en handig – de inhoud van de oorspronkelijke functie uitvoert.

als dat verwarrend klinkt, kijk dan eens naar de relevante broncode. Je zult zien dat het een zeer beknopte manier van het maken van een functie voldoen aan de Handler interface.

laten we de toepassing timeHandler reproduceren met behulp van deze techniek:

bestand: main.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 feite is het converteren van een functie naar een HandlerFunc type en het vervolgens toevoegen aan een ServeMux zoals deze zo gebruikelijk dat Go een snelkoppeling biedt: de mux.HandleFunc methode.

Dit is hoe de functie main() eruit zou hebben gezien als we in plaats daarvan deze sneltoets hadden gebruikt:

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

meestal werkt het gebruik van een functie als handler goed. Maar er is een beetje een beperking als de dingen complexer beginnen te worden.

u hebt waarschijnlijk gemerkt dat, in tegenstelling tot de methode daarvoor, we het tijdformaat in de timeHandler functie hardcoderen. Wat gebeurt er als we informatie of variabelen van main() aan een handler willen doorgeven?

een nette aanpak is om onze handler logica in een sluiting te zetten, en te sluiten over de variabelen die we willen gebruiken:

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() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

de functie timeHandler heeft nu een subtiel andere rol. In plaats van de functie in een handler te dwingen (zoals we eerder deden), gebruiken we het nu om een handler terug te sturen. Er zijn twee belangrijke elementen om dit te laten werken.

eerst maakt het fn, een anonieme functie die &dash opent; of sluit over – de format variabele die een afsluiting vormt. Ongeacht wat we doen met de sluiting zal het altijd toegang hebben tot de variabelen die lokaal zijn tot de scope waarin het is gemaakt – wat in dit geval betekent dat het altijd toegang heeft tot de format variabele.

ten tweede heeft onze afsluiting de handtekening func(http.ResponseWriter, *http.Request). Zoals u zich misschien herinnert van eerder, dit betekent dat we het kunnen omzetten in een HandlerFunc type (zodat het voldoet aan de Handler interface). Onze timeHandler functie geeft dan deze geconverteerde sluiting terug.

in dit voorbeeld geven we een eenvoudige string door aan een handler. Maar in een echte toepassing kunt u deze methode gebruiken om databaseverbinding, sjabloonkaart of een andere context op toepassingsniveau door te geven. Het is een goed alternatief voor het gebruik van globale variabelen, en heeft het extra voordeel van het maken van nette self-contained handlers Voor het testen.

u kunt hetzelfde patroon ook zien geschreven als:

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

of het gebruik van een impliciete conversie naar het HandlerFunc type bij retour:

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

de DefaultServeMux

u hebt waarschijnlijk DefaultServeMux op veel plaatsen gezien, van de eenvoudigste Hello World-voorbeelden tot de go-broncode.

Het kostte me een lange tijd om te beseffen dat het niets speciaals is. De DefaultServeMux is gewoon een gewone ol ‘ ServeMux zoals we al hebben gebruikt, die standaard wordt geïnstalleerd wanneer het HTTP pakket wordt gebruikt. Hier is de relevante regel van de go-bron:

var DefaultServeMux = NewServeMux()

over het algemeen moet u de DefaultServeMux niet gebruiken omdat het een veiligheidsrisico vormt.

omdat de DefaultServeMux is opgeslagen in een globale variabele, is elk pakket in staat om er toegang toe te krijgen en een route te registreren – inclusief alle pakketten van derden die uw toepassing importeert. Als een van die third-party pakketten is gecompromitteerd, ze kunnen de DefaultServeMux gebruiken om een kwaadaardige handler bloot aan het web.

dus als vuistregel is het een goed idee om de DefaultServeMux te vermijden, en in plaats daarvan je eigen lokaal gerichte ServeMux te gebruiken, zoals we tot nu toe hebben gedaan. Maar als je besluit om het te gebruiken…

het HTTP pakket biedt een paar snelkoppelingen voor het werken met de Standaardservemux: http.Handle en http.HandleFunc. Deze doen precies hetzelfde als hun namesake functies waar we al naar hebben gekeken, met het verschil dat ze handlers toevoegen aan de DefaultServeMux in plaats van een die je hebt gemaakt.

daarnaast zal ListenAndServe terugvallen op het gebruik van de Defaultserve als er geen andere handler wordt gegeven (dat wil zeggen, de tweede parameter is ingesteld op nil).

als laatste stap, laten we onze timeHandler applicatie bijwerken om in plaats daarvan de DefaultServeMux te gebruiken:

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

Als u deze blogpost leuk vond, vergeet dan niet mijn nieuwe boek te lezen over het bouwen van professionele webapplicaties met Go!

Volg mij op Twitter @ajmedwards.

alle codefragmenten in dit bericht zijn vrij te gebruiken onder de MIT-licentie.



Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.