Eine Zusammenfassung der Anforderungsbehandlung in Go

Zuletzt aktualisiert: 21st Januar 2018 Abgelegt unter: golang tutorial

Bei der Verarbeitung von HTTP-Anforderungen mit Go geht es in erster Linie um zwei Dinge: Servemuxe und Handler.

Ein ServeMux ist im Wesentlichen ein HTTP-Anforderungsrouter (oder Multiplexer). Es vergleicht eingehende Anforderungen mit einer Liste vordefinierter URL-Pfade und ruft den zugehörigen Handler für den Pfad auf, wenn eine Übereinstimmung gefunden wird.

Handler sind für das Schreiben von Antwortheadern und -körpern verantwortlich. Fast jedes Objekt kann ein Handler sein, solange es die http.Handler Schnittstelle erfüllt. In einfachen Worten bedeutet das einfach, dass es eine ServeHTTP -Methode mit der folgenden Signatur haben muss:

ServeHTTP(http.ResponseWriter, *http.Request)

Das HTTP-Paket von Go enthält einige Funktionen zum Generieren gängiger Handler, z. B. FileServerNotFoundHandler und RedirectHandler. Beginnen wir mit einem einfachen, aber erfundenen Beispiel:

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

Datei: main.gehen Sie

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

Lassen Sie uns dies schnell durchgehen:

  • In der main Funktion verwenden wir die http.NewServeMux Funktion, um einen leeren ServeMux zu erstellen.
  • Wir verwenden dann die http.RedirectHandler Funktion, um einen neuen Handler zu erstellen. Dieser Handler 307 leitet alle empfangenen Anfragen an http://example.org weiter.
  • Als nächstes verwenden wir die Funktion mux.Handle , um dies bei unserem neuen ServeMux zu registrieren, sodass es als Handler für alle eingehenden Anforderungen mit dem URL-Pfad /foo fungiert.
  • Schließlich erstellen wir einen neuen Server und beginnen mit der Funktion http.ListenAndServe auf eingehende Anfragen zu warten.

Führen Sie die Anwendung aus:

$ go run main.goListening...

Und besuchen Sie http://localhost:3000/foo in Ihrem Browser. Sie sollten feststellen, dass Ihre Anfrage erfolgreich umgeleitet wird.

Die Adleraugen von Ihnen haben vielleicht etwas Interessantes bemerkt: Die Signatur für die Funktion ListenAndServe lautet ListenAndServe(addr string, handler Handler) , aber wir haben einen ServeMux als zweiten Parameter übergeben.

Wir konnten dies tun, weil der ServeMux-Typ auch eine ServeHTTP -Methode hat, was bedeutet, dass auch er die Handler-Schnittstelle erfüllt.

Für mich vereinfacht es die Dinge, einen ServeMux nur als eine spezielle Art von Handler zu betrachten, der, anstatt eine Antwort selbst zu geben, die Anfrage an einen zweiten Handler weitergibt. Dies ist kein so großer Sprung, wie es sich zunächst anhört – das Verketten von Handlern ist in Go ziemlich üblich.

Benutzerdefinierte Handler

Erstellen wir einen benutzerdefinierten Handler, der mit der aktuellen Ortszeit in einem bestimmten Format antwortet:

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

Der genaue Code hier ist nicht allzu wichtig.

Alles, was wirklich zählt, ist, dass wir ein Objekt haben (in diesem Fall ist es eine timeHandler Struktur, aber es könnte auch ein String oder eine Funktion oder irgendetwas anderes sein), und wir haben eine Methode mit der Signatur ServeHTTP(http.ResponseWriter, *http.Request) implementiert. Das ist alles, was wir brauchen, um einen Handler zu machen.

Lassen Sie uns dies in ein konkretes Beispiel einbetten:

Datei: 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 der main Funktion initialisierten wir die timeHandler in genau der gleichen Weise würden wir jede normale Struktur, mit der & Symbol, um einen Zeiger zu erhalten. Und dann verwenden wir, wie im vorherigen Beispiel, die Funktion mux.Handle , um dies bei unserem ServeMux zu registrieren.

Wenn wir nun die Anwendung ausführen, übergibt der ServeMux jede Anforderung für /time direkt an unsere timeHandler.ServeHTTP -Methode.

Probieren Sie es aus: http://localhost:3000/time.

Beachten Sie auch, dass wir die timeHandler problemlos auf mehreren Routen wiederverwenden können:

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

Funktionen als Handler

Für einfache Fälle (wie das obige Beispiel) fühlt sich das Definieren neuer benutzerdefinierter Typen und ServeHTTP-Methoden etwas ausführlich an. Schauen wir uns einen alternativen Ansatz an, bei dem wir den http.HandlerFunc Typ von Go http.HandlerFunc , um eine normale Funktion zur Erfüllung der Handlerschnittstelle zu zwingen.

Jede Funktion mit der Signatur func(http.ResponseWriter, *http.Request) kann in einen HandlerFunc-Typ konvertiert werden. Dies ist nützlich, da HandleFunc-Objekte über eine integrierte ServeHTTP –Methode verfügen, die – ziemlich geschickt und bequem – den Inhalt der ursprünglichen Funktion ausführt.

Wenn das verwirrend klingt, schauen Sie sich den entsprechenden Quellcode an. Sie werden sehen, dass es eine sehr prägnante Art ist, eine Funktion in der Handler-Schnittstelle zu erstellen.

Lassen Sie uns die timeHandler-Anwendung mit dieser Technik reproduzieren:

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

Tatsächlich ist das Konvertieren einer Funktion in einen HandlerFunc Typ und das anschließende Hinzufügen zu einem ServeMux wie diesem so üblich, dass Go eine Verknüpfung bereitstellt: die mux.HandleFunc Methode.

So hätte die main() -Funktion ausgesehen, wenn wir stattdessen diese Verknüpfung verwendet hätten:

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

Die meiste Zeit funktioniert es gut, eine Funktion als Handler wie diese zu verwenden. Aber es gibt eine gewisse Einschränkung, wenn die Dinge komplexer werden.

Sie haben wahrscheinlich bemerkt, dass wir im Gegensatz zur vorherigen Methode das Zeitformat in der Funktion timeHandler fest codieren mussten. Was passiert, wenn wir Informationen oder Variablen von main() an einen Handler übergeben möchten?

Ein ordentlicher Ansatz besteht darin, unsere Handlerlogik in einen Abschluss zu bringen und die Variablen zu schließen, die wir verwenden möchten:

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

Die timeHandler Funktion hat jetzt eine subtil andere Rolle. Anstatt die Funktion in einen Handler zu zwingen (wie zuvor), verwenden wir sie jetzt, um einen Handler zurückzugeben. Es gibt zwei Schlüsselelemente, um diese Arbeit zu machen.

Zuerst erstellt es fn, eine anonyme Funktion, die auf ‐ zugreift; oder schließt – die format Variable, die einen Abschluss bildet. Unabhängig davon, was wir mit dem Abschluss tun, kann er immer auf die Variablen zugreifen, die lokal für den Bereich sind, in dem er erstellt wurde – was in diesem Fall bedeutet, dass er immer Zugriff auf die Variable format .

Zweitens hat unser Verschluss die Signatur func(http.ResponseWriter, *http.Request). Wie Sie sich vielleicht von früher erinnern, bedeutet dies, dass wir es in einen HandlerFunc-Typ konvertieren können (so dass es die Handler-Schnittstelle erfüllt). Unsere timeHandler Funktion gibt dann diesen konvertierten Abschluss zurück.

In diesem Beispiel haben wir gerade einen einfachen String an einen Handler übergeben. In einer realen Anwendung können Sie diese Methode jedoch verwenden, um eine Datenbankverbindung, eine Vorlagenzuordnung oder einen anderen Kontext auf Anwendungsebene zu übergeben. Es ist eine gute Alternative zur Verwendung globaler Variablen und bietet den zusätzlichen Vorteil, dass saubere, in sich geschlossene Handler zum Testen erstellt werden.

Möglicherweise wird dasselbe Muster auch wie folgt geschrieben:

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

Oder verwenden Sie bei der Rückgabe eine implizite Konvertierung in den Typ HandlerFunc:

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

Der DefaultServeMux

Sie haben wahrscheinlich gesehen, dass DefaultServeMux an vielen Stellen erwähnt wurde, von den einfachsten Hello World-Beispielen bis zum Go-Quellcode.

Ich habe lange gebraucht, um zu erkennen, dass es nichts Besonderes ist. Der DefaultServeMux ist nur ein einfacher alter ServeMux, wie wir ihn bereits verwendet haben und der standardmäßig instanziiert wird, wenn das HTTP-Paket verwendet wird. Hier ist die relevante Zeile aus der Go-Quelle:

var DefaultServeMux = NewServeMux()

Generell sollten Sie den DefaultServeMux nicht verwenden, da er ein Sicherheitsrisiko darstellt.

Da DefaultServeMux in einer globalen Variablen gespeichert ist, kann jedes Paket darauf zugreifen und eine Route registrieren – einschließlich aller Pakete von Drittanbietern, die Ihre Anwendung importiert. Wenn eines dieser Pakete von Drittanbietern kompromittiert wird, können sie den DefaultServeMux verwenden, um einen böswilligen Handler für das Web verfügbar zu machen.

Als Faustregel ist es also eine gute Idee, den DefaultServeMux zu vermeiden und stattdessen Ihren eigenen ServeMux mit lokalem Gültigkeitsbereich zu verwenden, wie wir es bisher getan haben. Aber wenn Sie sich entschieden haben, es zu benutzen…

Das HTTP-Paket bietet einige Verknüpfungen für die Arbeit mit DefaultServeMux: http .Handle und http.HandleFunc. Diese machen genau das gleiche wie ihre gleichnamigen Funktionen, die wir uns bereits angesehen haben, mit dem Unterschied, dass sie dem DefaultServeMux Handler anstelle eines von Ihnen erstellten hinzufügen.

Zusätzlich wird ListenAndServe auf die Verwendung von DefaultServeMux zurückgreifen, wenn kein anderer Handler bereitgestellt wird (dh der zweite Parameter ist auf nil ).

Als letzten Schritt aktualisieren wir unsere timeHandler-Anwendung, um stattdessen den DefaultServeMux zu verwenden:

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

Wenn Ihnen dieser Blogbeitrag gefallen hat, vergessen Sie nicht, mein neues Buch über das Erstellen professioneller Webanwendungen mit Go zu lesen!

Folgen Sie mir auf Twitter @ajmedwards.

Alle Code-Schnipsel in diesem Beitrag sind frei unter der MIT-Lizenz zu verwenden.



Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.