En sammanfattning av begäran hantering I Go

Senast uppdaterad: 21 januari 2018 Filed under: golang tutorial

bearbetning HTTP-förfrågningar med Go handlar främst om två saker: ServeMuxes och hanterare.

en ServeMux är i huvudsak en HTTP – begäran router (eller multiplexor). Den jämför inkommande förfrågningar mot en lista med fördefinierade URL-sökvägar och anropar den associerade hanteraren för Sökvägen när en matchning hittas.

hanterare är ansvariga för att skriva svarhuvuden och organ. Nästan alla objekt kan vara en hanterare, så länge det uppfyller gränssnittet http.Handler. I lay-termer betyder det helt enkelt att det måste ha en ServeHTTP metod med följande signatur:

ServeHTTP(http.ResponseWriter, *http.Request)

Go: s HTTP-paket levereras med några funktioner för att generera vanliga hanterare, till exempel FileServerNotFoundHandler och RedirectHandler. Låt oss börja med ett enkelt men konstruerat exempel:

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

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

låt oss gå igenom detta snabbt:

  • i funktionen main vi använder funktionen http.NewServeMux för att skapa en tom servemux.
  • vi använder sedan funktionen http.RedirectHandler för att skapa en ny hanterare. Denna hanterare 307 omdirigerar alla förfrågningar som den tar emot till http://example.org.
  • Nästa använder vi funktionen mux.Handle för att registrera detta med vår nya ServeMux, så den fungerar som Hanterare för alla inkommande förfrågningar med URL-sökvägen /foo.
  • slutligen skapar vi en ny server och börjar lyssna på inkommande förfrågningar med funktionen http.ListenAndServe, som passerar i vår ServeMux för att den ska matcha förfrågningar mot.

gå vidare och kör programmet:

$ go run main.goListening...

och besök http://localhost:3000/foo I din webbläsare. Du bör finna att din begäran blir framgångsrikt omdirigeras.du kanske har märkt något intressant: signaturen för ListenAndServe-funktionen är ListenAndServe(addr string, handler Handler), men vi passerade en ServeMux som den andra parametern.

vi kunde göra detta eftersom ServeMux-typen också har enServeHTTP – metod, vilket innebär att den också uppfyller hanteringsgränssnittet.

för mig förenklar det saker att tänka på en ServeMux som bara en speciell typ av hanterare, som istället för att ge ett svar själv skickar begäran till en andra hanterare. Det här är inte så mycket av ett steg som det först låter – kedjehanterare tillsammans är ganska vanligt I Go.

anpassade hanterare

Låt oss skapa en anpassad hanterare som svarar med aktuell lokal tid i ett 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 exakta koden här är inte så viktig.

allt som verkligen betyder något är att vi har ett objekt (i det här fallet är det ett timeHandler struct, men det kan också vara en sträng eller funktion eller något annat), och vi har implementerat en metod med signaturen ServeHTTP(http.ResponseWriter, *http.Request) på den. Det är allt vi behöver för att göra en handler.

låt oss bädda in detta i ett konkret exempel:

File: 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 funktionen main vi initierade timeHandler på exakt samma sätt som vi skulle någon normal struktur, med hjälp av & symbol för att ge en pekare. Och sedan, som föregående exempel, använder vi funktionen mux.Handle för att registrera detta med vår ServeMux.

Nu när vi kör programmet kommer ServeMux att skicka någon begäran om/time direkt till vårtimeHandler.ServeHTTP metod.

gå vidare och ge det ett försök: http://localhost:3000/time.

Lägg märke till att vi enkelt kan återanvända timeHandler på flera rutter:

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

fungerar som hanterare

för enkla fall (som exemplet ovan) definierar nya anpassade typer och servehttp metoder känns lite utförlig. Låt oss titta på ett alternativt tillvägagångssätt, där vi utnyttjar Go: s http.HandlerFunc typ för att tvinga en normal funktion till att uppfylla hanteringsgränssnittet.

alla funktioner som har signaturen func(http.ResponseWriter, *http.Request) kan konverteras till en HandlerFunc-typ. Detta är användbart eftersom HandleFunc-objekt levereras med en inbyggd ServeHTTP metod som – ganska smart och bekvämt – utför innehållet i den ursprungliga funktionen.

om det låter förvirrande, försök ta en titt på relevant källkod. Du ser att det är ett mycket kortfattat sätt att få en funktion att tillfredsställa hanterarens gränssnitt.

låt oss reproducera timeHandler-applikationen med den här tekniken:

fil: Huvud.gå

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

faktum är att konvertera en funktion till en HandlerFunc-typ och sedan lägga till den i en ServeMux så här är så vanligt att Go ger en genväg: metoden mux.HandleFunc.

det här är vad funktionen main() skulle ha sett ut om vi hade använt den här genvägen istället:

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

För det mesta med en fungerar som en hanterare som detta fungerar bra. Men det är lite av en begränsning när saker börjar bli mer komplexa.

Du har säkert märkt att vi, till skillnad från metoden tidigare, måste hårdkoda tidsformatet i funktionen timeHandler. Vad händer när vi vill skicka information eller variabler från main() till en hanterare?

ett snyggt tillvägagångssätt är att sätta vår hanteringslogik i en stängning och stänga över de variabler vi vill använda:

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

funktionen timeHandler har nu en subtilt annorlunda roll. Istället för att tvinga funktionen till en hanterare (som vi gjorde tidigare) använder vi den nu för att returnera en hanterare. Det finns två viktiga element för att göra detta arbete.

först skapar fn, en anonym funktion som kommer åt &streck; eller stänger över – format variabel som bildar en stängning. Oavsett vad vi gör med stängningen kommer det alltid att kunna komma åt variablerna som är lokala till det omfång det skapades i – vilket i det här fallet betyder att det alltid har tillgång till format variabel.

För det andra har vår stängning signaturen func(http.ResponseWriter, *http.Request). Som du kanske kommer ihåg från tidigare betyder det att vi kan konvertera den till en Hanterfunc-typ (så att den uppfyller hanterarens gränssnitt). Vår timeHandler – funktion returnerar sedan denna konverterade stängning.

i det här exemplet har vi bara överfört en enkel sträng till en hanterare. Men i en verklig applikation kan du använda den här metoden för att skicka databasanslutning, mallkarta eller något annat programkontext. Det är ett bra alternativ till att använda globala variabler, och har den extra fördelen att göra snygga fristående Hanterare för testning.

Du kan också se samma mönster skrivet 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 använda en implicit konvertering till typen HandlerFunc vid 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)) }}

DefaultServeMux

Du har förmodligen sett DefaultServeMux nämns på många ställen, från de enklaste Hello World-exemplen till go-källkoden.

det tog mig lång tid att inse att det inte är något speciellt. DefaultServeMux är bara en vanlig ol ’ ServeMux som vi redan har använt, vilket blir instansierat som standard när HTTP-paketet används. Här är den relevanta raden från Go-källan:

var DefaultServeMux = NewServeMux()

Generellt bör du inte använda DefaultServeMux eftersom det utgör en säkerhetsrisk.

eftersom DefaultServeMux lagras i en global variabel kan alla paket komma åt den och registrera en rutt – inklusive alla tredjepartspaket som din applikation importerar. Om ett av dessa tredjepartspaket äventyras kan de använda DefaultServeMux för att exponera en skadlig hanterare på webben.

så som en tumregel är det bra att undvika DefaultServeMux, och istället använda din egen lokalt scoped ServeMux, som vi har varit hittills. Men om du bestämde dig för att använda den…

HTTP-paketet innehåller ett par genvägar för att arbeta med DefaultServeMux: http.Hantera och http.HandleFunc. Dessa gör exakt samma som deras namnefunktioner som vi redan har tittat på, med skillnaden att de lägger till hanterare till DefaultServeMux istället för en som du har skapat.

Dessutom kommer ListenAndServe att falla tillbaka till att använda DefaultServeMux om ingen annan hanterare tillhandahålls (det vill säga den andra parametern är inställd på nil).

så som ett sista steg, låt oss uppdatera vår timeHandler-applikation för att använda DefaultServeMux istället:

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

om du gillade det här blogginlägget, glöm inte att kolla in min nya bok om hur man bygger professionella webbapplikationer med Go!

Följ mig på Twitter @ajmedwards.

alla kodavsnitt i det här inlägget är gratis att använda under MIT-licensen.



Lämna ett svar

Din e-postadress kommer inte publiceras.