podsumowanie obsługi żądań w Go

Ostatnia aktualizacja: 21st January 2018 Filed under: golang tutorial

przetwarzanie żądań HTTP w Go to przede wszystkim dwie rzeczy: Serwemuksy i obsługa.

ServeMux jest zasadniczo routerem żądania HTTP (lub multipleksorem). Porównuje przychodzące żądania z listą wstępnie zdefiniowanych ścieżek URL i wywołuje skojarzony moduł obsługi ścieżki, gdy tylko znajdzie się dopasowanie.

manipulatory są odpowiedzialne za pisanie nagłówków odpowiedzi i ciał. Prawie każdy obiekt może być Obsługą, o ile spełnia interfejshttp.Handler. W kategoriach lay oznacza to po prostu, że musi mieć ServeHTTP metoda z następującym podpisem:

ServeHTTP(http.ResponseWriter, *http.Request)

pakiet HTTP Go zawiera kilka funkcji do generowania popularnych programów obsługi, takich jak FileServerNotFoundHandler I RedirectHandler. Zacznijmy od prostego, ale wymyślnego przykładu:

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

plik: main.przejdź

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

przejdźmy przez to szybko:

  • w funkcji main używamy funkcji http.NewServeMux aby utworzyć pusty servemux.
  • następnie używamy funkcjihttp.RedirectHandler, aby utworzyć nową funkcję obsługi. Ten moduł obsługi 307 przekierowuje wszystkie otrzymane żądania do http://example.org.
  • następnie używamy funkcjimux.Handle, aby zarejestrować to w naszym nowym ServeMux, więc działa jako Obsługa wszystkich przychodzących żądań ze ścieżką URL/foo.
  • w końcu tworzymy nowy serwer i zaczynamy nasłuchiwać przychodzących żądań za pomocą funkcjihttp.ListenAndServe, przekazując nasz ServeMux, aby dopasować żądania.

uruchom aplikację:

$ go run main.goListening...

i odwiedźhttp://localhost:3000/foo w swojej przeglądarce. Powinieneś zauważyć, że twoje żądanie zostanie pomyślnie przekierowane.

ktoś z Was mógł zauważyć coś ciekawego: sygnatura funkcji ListenAndServe toListenAndServe(addr string, handler Handler), ale jako drugi parametr przekazaliśmy ServeMux.

udało nam się to zrobić, ponieważ Typ ServeMux ma również metodę ServeHTTP, co oznacza, że również spełnia interfejs obsługi.

dla mnie upraszcza to myślenie o ServeMux jako o specjalnym rodzaju obsługi, która zamiast dać odpowiedź sama przekazuje żądanie do drugiego obsługi. To nie jest tak duży skok, jak się wydaje-łączenie łańcuchów razem jest dość powszechne w Go.

niestandardowe programy obsługi

stwórzmy niestandardowy program obsługi, który odpowiada bieżącym czasem lokalnym w danym formacie:

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

dokładny kod tutaj nie jest zbyt ważny.

wszystko, co naprawdę ma znaczenie, to to, że mamy obiekt (w tym przypadku jest totimeHandler struct, ale równie dobrze może to być łańcuch lub funkcja lub cokolwiek innego) i zaimplementowaliśmy metodę z podpisemServeHTTP(http.ResponseWriter, *http.Request) na nim. To wszystko, czego potrzebujemy, by zostać opiekunem.

osadzmy to w konkretnym przykładzie:

File: main.przejdź

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

w funkcji main zainicjowaliśmy timeHandler w dokładnie taki sam sposób, jak każdy normalny struct, używając & symbol dający wskaźnik. Następnie, podobnie jak w poprzednim przykładzie, używamy funkcjimux.Handle, aby zarejestrować to w naszym ServeMux.

teraz, gdy uruchomimy aplikację, ServeMux przekaże każde żądanie dla /time prosto do naszej timeHandler.ServeHTTP metody.

śmiało i spróbuj: http://localhost:3000/time.

zauważ również, że możemy łatwo użyć timeHandler w wielu trasach:

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

działa jako programy obsługi

dla prostych przypadków (jak w powyższym przykładzie) definiowanie nowych niestandardowych typów i metod servehttp wydaje się nieco gadatliwe. Przyjrzyjmy się alternatywnemu podejściu, w którym wykorzystujemy Go http.HandlerFunc, aby zmusić normalną funkcję do spełnienia interfejsu obsługi.

każda funkcja, która ma podpis func(http.ResponseWriter, *http.Request) może zostać przekonwertowana na typ HandlerFunc. Jest to użyteczne, ponieważ obiekty HandleFunc posiadają wbudowaną metodę ServeHTTP, która – dość sprytnie i wygodnie – wykonuje zawartość oryginalnej funkcji.

jeśli brzmi to myląco, spróbuj spojrzeć na odpowiedni kod źródłowy. Zobaczysz, że jest to bardzo zwięzły sposób na to, aby funkcja spełniała interfejs obsługi.

odtwarzajmy aplikację timeHandler używając tej techniki:

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

w rzeczywistości konwersja funkcji do typu HandlerFunc, a następnie dodanie jej do ServeMux w ten sposób jest tak powszechne, że go zapewnia skrót:mux.HandleFunc metoda.

tak wyglądałaby funkcja main(), gdybyśmy zamiast tego użyli tego skrótu:

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

większość czasu używa funkcja obsługi jak to działa dobrze. Ale istnieje pewne ograniczenie, gdy sprawy zaczynają się coraz bardziej skomplikowane.

prawdopodobnie zauważyłeś, że w przeciwieństwie do metody wcześniej musieliśmy zakodować format czasu w funkcji timeHandler. Co się dzieje, gdy chcemy przekazać informacje lub zmienne z main() do modułu obsługi?

dobrym podejściem jest umieszczenie naszej logiki obsługi w zamknięciu i zamknięcie nad zmiennymi, których chcemy użyć:

File: main.przejdź

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

funkcjatimeHandler ma teraz subtelnie inną rolę. Zamiast zmuszać funkcję do obsługi (tak jak robiliśmy to wcześniej), teraz używamy jej do zwracania obsługi. Są dwa kluczowe elementy, aby to zadziałało.

najpierw tworzyfn, anonimową funkcję, która uzyskuje dostęp do‐ lub zamyka zmiennąformat tworząc zamknięcie. Niezależnie od tego, co zrobimy z zamknięciem, zawsze będzie mógł uzyskać dostęp do zmiennych lokalnych do zakresu, w którym został utworzony-co w tym przypadku oznacza, że zawsze będzie miał dostęp do zmiennej format.

Po drugie nasze zamknięcie ma podpis func(http.ResponseWriter, *http.Request). Jak zapewne pamiętasz z poprzedniego, oznacza to, że możemy go przekonwertować na typ HandlerFunc (tak, że spełnia on interfejs obsługi). Nasza funkcjatimeHandler zwraca to przekonwertowane zamknięcie.

w tym przykładzie właśnie przekazywaliśmy prosty łańcuch do modułu obsługi. Ale w rzeczywistym świecie aplikacji można użyć tej metody do przekazania połączenia z bazą danych, mapy szablonu lub dowolnego innego kontekstu na poziomie aplikacji. Jest to dobra alternatywa dla korzystania ze zmiennych globalnych i ma dodatkową zaletę tworzenia samodzielnych programów obsługi do testowania.

Możesz również zobaczyć ten sam wzorzec zapisany jako:

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

lub używając domyślnej konwersji do typu HandlerFunc po powrocie:

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

Prawdopodobnie widziałeś defaultservemux wymienione w wielu miejscach, od najprostszych przykładów Hello World do kodu źródłowego Go.

dużo czasu zajęło mi uświadomienie sobie, że to nic specjalnego. DefaultServeMux to zwykły stary ServeMux, jakiego już używaliśmy, który domyślnie tworzy instancję, gdy używany jest pakiet HTTP. Oto odpowiednia linia ze źródła Go:

var DefaultServeMux = NewServeMux()

generalnie nie powinieneś używać DefaultServeMux, ponieważ stwarza to zagrożenie bezpieczeństwa.

ponieważ DefaultServeMux jest przechowywany w zmiennej globalnej, każdy pakiet jest w stanie uzyskać do niego dostęp i zarejestrować trasę – w tym wszystkie pakiety innych firm importowane przez aplikację. Jeśli jeden z tych pakietów jest zagrożony, mogą użyć DefaultServeMux do ujawnienia szkodliwego programu obsługi w sieci.

więc z reguły dobrym pomysłem jest unikanie DefaultServeMux, a zamiast tego używanie własnego lokalnego ServeMux, tak jak do tej pory. Ale jeśli zdecydujesz się go użyć…

pakiet HTTP zawiera kilka skrótów do pracy z DefaultServeMux: http.Uchwyt i http.HandleFunc. Działają one dokładnie tak samo, jak ich funkcje imienne, na które już patrzyliśmy, z tą różnicą, że dodają funkcje obsługi do DefaultServeMux zamiast tych, które utworzyłeś.

dodatkowo, ListenAndServe powróci do użycia DefaultServeMux, jeśli nie zostanie dostarczony żaden inny moduł obsługi (to znaczy, że drugi parametr jest ustawiony na nil).

więc jako ostatni krok, zaktualizujmy naszą aplikację timeHandler, aby zamiast tego użyć 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)}

Jeśli podobał Ci się ten post na blogu, nie zapomnij sprawdzić mojej nowej książki o tym, jak budować profesjonalne aplikacje internetowe z Go!

Śledź mnie na Twitterze @ajmedwards.

wszystkie fragmenty kodu w tym poście są dostępne za darmo na licencji MIT.



Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.