Eine Zusammenfassung der Anforderungsbehandlung in Go
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. FileServer
NotFoundHandler
und RedirectHandler
. Beginnen wir mit einem einfachen, aber erfundenen Beispiel:
Lassen Sie uns dies schnell durchgehen:
- In der
main
Funktion verwenden wir diehttp.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 anhttp://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:
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:
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:
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:
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:
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:
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:
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:
Oder verwenden Sie bei der Rückgabe eine implizite Konvertierung in den Typ HandlerFunc:
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:
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.