En oversigt over Anmodningshåndtering I Go

sidst opdateret: 21. januar 2018 arkiveret under: golang tutorial

behandling af HTTP-anmodninger med Go handler primært om to ting: servere og handlere.

en server er i det væsentlige en HTTP-anmodningsrouter (eller multipleksor). Den sammenligner indgående anmodninger med en liste over foruddefinerede URL-stier, og kalder den tilknyttede handler for stien, når der findes et match.

handlere er ansvarlige for at skrive svar overskrifter og organer. Næsten ethvert objekt kan være en handler, så længe det opfylder http.Handler interface. I lay termer betyder det simpelthen, at det skal have en ServeHTTP metode med følgende signatur:

ServeHTTP(http.ResponseWriter, *http.Request)

Go ‘ S HTTP-pakke sendes med et par funktioner til at generere fælles handlere, såsom FileServerNotFoundHandler og RedirectHandler. Lad os begynde med et simpelt, men konstrueret eksempel:

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

fil: main.gå

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

lad os gå igennem dette hurtigt:

  • i main funktion vi bruger http.NewServeMux funktion til at oprette en tom servemuk.
  • Vi bruger derefter funktionenhttp.RedirectHandler til at oprette en ny handler. Denne handler 307 omdirigerer alle anmodninger, den modtager, til http://example.org.
  • næste bruger vimux.Handlefunktionen til at registrere dette med vores nye Servemiks, så det fungerer som håndterer for alle indgående anmodninger med URL-stien/foo.
  • endelig opretter vi en ny server og begynder at lytte til indgående anmodninger medhttp.ListenAndServe – funktionen, der passerer i vores Servemøder for at matche anmodninger imod.

gå videre og kør applikationen:

$ go run main.goListening...

og besøghttp://localhost:3000/foo i din bro.ser. Du bør opdage, at din anmodning bliver omdirigeret.

du har måske bemærket noget interessant: signaturen til ListenAndServe-funktionen er ListenAndServe(addr string, handler Handler), men vi bestod en Servemuks som den anden parameter.

Vi var i stand til at gøre dette, fordi Servemuks-typen også har en ServeHTTP – metode, hvilket betyder, at den også tilfredsstiller Handlergrænsefladen.

for mig forenkler det ting at tænke på en Servemuk som bare at være en speciel slags handler, som i stedet for at give et svar selv videregiver anmodningen til en anden handler. Dette er ikke så meget af et spring, som det først lyder – at kæde håndterere sammen er ret almindeligt i Go.

brugerdefinerede handlere

lad os oprette en brugerdefineret handler, der reagerer med den aktuelle lokale tid i et givet format:

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

den nøjagtige kode her er ikke for vigtig.

alt, hvad der virkelig betyder noget, er, at vi har et objekt (i dette tilfælde er det en timeHandler struct, men det kan ligeledes være en streng eller funktion eller noget andet), og vi har implementeret en metode med signaturen ServeHTTP(http.ResponseWriter, *http.Request) på den. Det er alt, hvad vi behøver for at lave en handler.

lad os integrere dette i et konkret eksempel:

fil: main.gå

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

i main funktionen initialiserede vi timeHandler på nøjagtig samme måde ville vi enhver normal struktur ved hjælp af & symbol for at give en markør. Og så, som det foregående eksempel, bruger vi mux.Handle – funktionen til at registrere dette med vores Servemuk.

nu når vi kører applikationen, sender Servemuks enhver anmodning om /timelige videre til vores timeHandler.ServeHTTP metode.

gå videre og prøv det: http://localhost:3000/time.

Bemærk også, at vi nemt kunne genbruge timeHandler i flere ruter:

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

fungerer som håndterere

i enkle tilfælde (som eksemplet ovenfor) føles det lidt detaljeret at definere nye brugerdefinerede typer og servehttp-metoder. Lad os se på en alternativ tilgang, hvor vi udnytter Go ‘ s http.HandlerFunc type for at tvinge en normal funktion til at tilfredsstille handlerens grænseflade.

enhver funktion, der har signaturen func(http.ResponseWriter, *http.Request) kan konverteres til en HandlerFunc type. Dette er nyttigt, fordi HandleFunc objekter kommer med en indbygget ServeHTTP metode, som – temmelig smart og bekvemt – udfører indholdet af den oprindelige funktion.

Hvis det lyder forvirrende, kan du prøve at se på den relevante kildekode. Du vil se, at det er en meget kortfattet måde at få en funktion til at tilfredsstille handlerens interface.

lad os gengive timeHandler-applikationen ved hjælp af denne teknik:

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

faktisk er konvertering af en funktion til en HandlerFunc-type og derefter tilføjelse til en Servemuk som denne så almindelig, at Go giver en genvej: mux.HandleFunc metode.

Dette er, hvad main() funktionen ville have set ud, hvis vi havde brugt denne genvej i stedet:

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

det meste af tiden ved hjælp af en funktion som en handler som denne fungerer godt. Men der er lidt af en begrænsning, når tingene begynder at blive mere komplekse.

Du har sikkert bemærket, at vi i modsætning til metoden før har været nødt til at hardcode tidsformatet itimeHandler – funktionen. Hvad sker der, når vi ønsker at videregive oplysninger eller variabler fra main() til en handler?

en pæn tilgang er at sætte vores handlingslogik i en lukning og lukke over de variabler, vi vil bruge:

File: main.gå

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

timeHandler funktionen har nu en subtilt anden rolle. I stedet for at tvinge funktionen til en handler (som vi gjorde tidligere), bruger vi den nu til at returnere en handler. Der er to centrale elementer til at gøre dette arbejde.

først opretter det fn, en anonym funktion, der får adgang til ‐ eller lukker over – format variabel, der danner en lukning. Uanset hvad vi gør med lukningen, vil den altid kunne få adgang til de variabler, der er lokale til det omfang, det blev oprettet i – hvilket i dette tilfælde betyder, at det altid har adgang til format variablen.

for det andet har vores lukning signaturenfunc(http.ResponseWriter, *http.Request). Som du måske husker fra tidligere, betyder det, at vi kan konvertere den til en HandlerFunc-type (så den tilfredsstiller handlerens interface). Vores timeHandler funktion returnerer derefter denne konverterede lukning.

i dette eksempel har vi lige sendt en simpel streng til en handler. Men i en rigtig applikation kan du bruge denne metode til at videregive databaseforbindelse, skabelonkort eller enhver anden kontekst på applikationsniveau. Det er et godt alternativ til at bruge globale variabler, og har den ekstra fordel at gøre pæne selvstændige handlere til test.

Du kan også se det samme mønster skrevet som:

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

eller ved hjælp af en implicit konvertering til HandlerFunc-typen ved retur:

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

Defaultservemuks nævnt mange steder, fra de enkleste eksempler på Hej Verden til go-kildekoden.

det tog mig lang tid at indse, at det ikke er noget særligt. Som vi allerede har brugt, som bliver instantieret som standard, når HTTP-pakken bruges. Her er den relevante linje fra Go-kilden:

var DefaultServeMux = NewServeMux()

generelt bør du ikke bruge standardindstillingen, fordi den udgør en sikkerhedsrisiko.da standardindstillingerne er gemt i en global variabel, kan enhver pakke få adgang til den og registrere en rute – herunder eventuelle tredjepartspakker, som din applikation importerer. Hvis en af disse tredjepartspakker er kompromitteret, kan de bruge til at udsætte en ondsindet handler til internettet.

så som en tommelfingerregel er det en god ide at undgå Defaultservemuk, og i stedet bruge din egen lokalt scoped Servemuk, som vi har været indtil videre. Men hvis du beslutter dig for at bruge det…

HTTP-pakken indeholder et par genveje til at arbejde med standardindstillingerne: http.Håndtag og http.HandleFunc. Disse gør nøjagtigt det samme som deres navngivningsfunktioner, som vi allerede har set på, med den forskel, at de tilføjer handlere til standardindstillingerne i stedet for en, du har oprettet.

derudover vil ListenAndServe falde tilbage til at bruge standardindstillingen, hvis der ikke findes nogen anden handler (det vil sige den anden parameter er indstillet tilnil).

så som et sidste trin, lad os opdatere vores timeHandler-applikation for at bruge standardindstillingen i stedet:

File: main.gå

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

Hvis du nød dette blogindlæg, så glem ikke at tjekke min nye bog om, hvordan man bygger professionelle internetapplikationer med Go!

Følg mig på Facebook.

alle kodestykker i dette indlæg er gratis at bruge under MIT-licensen.



Skriv et svar

Din e-mailadresse vil ikke blive publiceret.