Gin ist ein in Go (Golang) geschriebenes HTTP-Webframework. Es verfügt über eine Martini-ähnliche API, jedoch mit einer bis zu 40-mal schnelleren Leistung als Martini. Wenn Sie eine umwerfende Leistung brauchen, besorgen Sie sich Gin.
Die offizielle Website von Gin stellt sich als Web-Framework mit „hoher Leistung“ und „guter Produktivität“ vor. Es werden auch zwei weitere Bibliotheken erwähnt. Der erste ist Martini, der ebenfalls ein Web-Framework ist und den Namen eines Likörs trägt. Gin sagt, dass es seine API nutzt, aber 40-mal schneller ist. Die Verwendung von httprouter ist ein wichtiger Grund, warum es 40-mal schneller als Martini sein kann.
Unter den „Funktionen“ auf der offiziellen Website sind acht Hauptfunktionen aufgeführt, und wir werden die Implementierung dieser Funktionen später schrittweise sehen.
Sehen wir uns das kleinste Beispiel in der offiziellen Dokumentation an.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Führen Sie dieses Beispiel aus und verwenden Sie dann einen Browser, um http://localhost:8080/ping aufzurufen, und Sie erhalten ein „Pong“.
Dieses Beispiel ist sehr einfach. Es kann in nur drei Schritte unterteilt werden:
Aus der GET-Methode im obigen kleinen Beispiel können wir ersehen, dass in Gin die Verarbeitungsmethoden von HTTP-Methoden mithilfe der entsprechenden Funktionen mit demselben Namen registriert werden müssen.
Es gibt neun HTTP-Methoden, und die vier am häufigsten verwendeten sind GET, POST, PUT und DELETE, die den vier Funktionen Abfragen, Einfügen, Aktualisieren und Löschen entsprechen. Es ist zu beachten, dass Gin auch die Any-Schnittstelle bereitstellt, mit der alle HTTP-Methodenverarbeitungsmethoden direkt an eine Adresse gebunden werden können.
Das zurückgegebene Ergebnis besteht im Allgemeinen aus zwei oder drei Teilen. Der Code und die Nachricht sind immer vorhanden, und Daten werden im Allgemeinen zur Darstellung zusätzlicher Daten verwendet. Wenn keine zusätzlichen Daten zurückgegeben werden müssen, kann diese weggelassen werden. Im Beispiel ist 200 der Wert des Codefelds und „pong“ der Wert des Nachrichtenfelds.
Im obigen Beispiel wurde gin.Default() zum Erstellen der Engine verwendet. Diese Funktion ist jedoch ein Wrapper für New. Tatsächlich wird die Engine über die neue Schnittstelle erstellt.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Werfen Sie zunächst einen kurzen Blick auf den Erstellungsprozess und konzentrieren Sie sich nicht auf die Bedeutung verschiedener Mitgliedsvariablen in der Engine-Struktur. Es ist ersichtlich, dass New zusätzlich zum Erstellen und Initialisieren einer Engine-Variablen vom Typ Engine auch engine.pool.New auf eine anonyme Funktion setzt, die engine.allocateContext() aufruft. Die Funktion dieser Funktion wird später besprochen.
In der Engine gibt es eine eingebettete Struktur RouterGroup. Die Schnittstellen, die sich auf HTTP-Methoden der Engine beziehen, werden alle von RouterGroup geerbt. Die „Routengruppierung“ in den auf der offiziellen Website erwähnten Funktionspunkten wird durch die RouterGroup-Struktur erreicht.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Jede RouterGroup ist einem Basispfad basePath zugeordnet. Der Basispfad der in die Engine eingebetteten RouterGroup ist „/“.
Es gibt auch eine Reihe von Verarbeitungsfunktionen-Handlern. Alle Anfragen unter den dieser Gruppe zugeordneten Pfaden führen zusätzlich die Verarbeitungsfunktionen dieser Gruppe aus, die hauptsächlich für Middleware-Aufrufe verwendet werden. Handlers ist gleich Null, wenn die Engine erstellt wird, und eine Reihe von Funktionen kann über die Use-Methode importiert werden. Wir werden diese Verwendung später sehen.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Die Handle-Methode von RouterGroup ist der letzte Eintrag für die Registrierung aller HTTP-Methoden-Callback-Funktionen. Die GET-Methode und andere Methoden im Zusammenhang mit HTTP-Methoden, die im ersten Beispiel aufgerufen wurden, sind nur Wrapper für die Handle-Methode.
Die Handle-Methode berechnet den absoluten Pfad gemäß dem BasePath der RouterGroup und dem relativen Pfadparameter und ruft gleichzeitig die CombineHandlers-Methode auf, um das endgültige Handler-Array abzurufen. Diese Ergebnisse werden als Parameter an die addRoute-Methode der Engine übergeben, um die Verarbeitungsfunktionen zu registrieren.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Was die Methode „combineHandlers“ tut, ist, ein Slice „mergedHandlers“ zu erstellen, dann die Handler der RouterGroup selbst hinein zu kopieren, dann die Handler der Parameter hinein zu kopieren und schließlich „mergedHandlers“ zurückzugeben. Das heißt, wenn eine beliebige Methode mit Handle registriert wird, umfasst das tatsächliche Ergebnis die Handler der RouterGroup selbst.
In dem auf der offiziellen Website erwähnten Feature-Punkt „Fast“ wird erwähnt, dass die Weiterleitung von Netzwerkanforderungen basierend auf dem Radix-Baum (Radix Tree) implementiert wird. Dieser Teil wird nicht von Gin implementiert, sondern von httprouter, der in der Einführung von Gin am Anfang erwähnt wurde. Gin verwendet httprouter, um diesen Teil der Funktion zu erreichen. Auf die Implementierung des Radix-Baums wird hier vorerst nicht eingegangen. Wir werden uns vorerst nur auf seine Verwendung konzentrieren. Vielleicht schreiben wir später einen separaten Artikel über die Implementierung des Radix-Baums.
In der Engine gibt es eine Trees-Variable, die ein Teil der methodTree-Struktur ist. Es ist diese Variable, die Verweise auf alle Basisbäume enthält.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Die Engine verwaltet einen Basisbaum für jede HTTP-Methode. Der Wurzelknoten dieses Baums und der Name der Methode werden zusammen in einer methodTree-Variablen gespeichert, und alle methodTree-Variablen befinden sich in Bäumen.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Es ist ersichtlich, dass in der addRoute-Methode der Engine zunächst die get-Methode von Bäumen verwendet wird, um den Wurzelknoten des Basisbaums abzurufen, der der Methode entspricht. Wenn der Wurzelknoten des Basisbaums nicht erhalten wird, bedeutet dies, dass noch keine Methode für diese Methode registriert wurde und ein Baumknoten als Wurzelknoten des Baums erstellt und zu den Bäumen hinzugefügt wird.
Verwenden Sie nach dem Abrufen des Stammknotens die addRoute-Methode des Stammknotens, um eine Reihe von Verarbeitungsfunktionshandlern für den Pfadpfad zu registrieren. In diesem Schritt wird ein Knoten für Pfad und Handler erstellt und im Basisbaum gespeichert. Wenn Sie versuchen, eine bereits registrierte Adresse zu registrieren, gibt addRoute direkt einen Panikfehler aus.
Bei der Verarbeitung einer HTTP-Anfrage muss der Wert des entsprechenden Knotens über den Pfad ermittelt werden. Der Wurzelknoten verfügt über eine getValue-Methode, die für die Abwicklung des Abfragevorgangs verantwortlich ist. Wir werden dies erwähnen, wenn wir über die Verarbeitung von HTTP-Anfragen durch Gin sprechen.
Die Use-Methode von RouterGroup kann eine Reihe von Middleware-Verarbeitungsfunktionen importieren. Die „Middleware-Unterstützung“ in den auf der offiziellen Website genannten Funktionspunkten wird durch die Use-Methode erreicht.
Im ersten Beispiel wurde beim Erstellen der Engine-Strukturvariablen nicht „New“, sondern „Default“ verwendet. Werfen wir einen Blick auf die zusätzlichen Funktionen von Default.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Es ist ersichtlich, dass es sich um eine sehr einfache Funktion handelt. Zusätzlich zum Aufruf von New zum Erstellen des Engine-Objekts wird nur Use aufgerufen, um die Rückgabewerte von zwei Middleware-Funktionen, Logger und Recovery, zu importieren. Der Rückgabewert von Logger ist eine Funktion zur Protokollierung, und der Rückgabewert von Recovery ist eine Funktion zur Panikbehandlung. Wir überspringen dies vorerst und schauen uns diese beiden Funktionen später an.
Obwohl die Engine RouterGroup einbettet, implementiert sie auch die Use-Methode, es handelt sich jedoch lediglich um einen Aufruf der Use-Methode von RouterGroup und einiger Hilfsoperationen.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Es ist ersichtlich, dass die Use-Methode von RouterGroup ebenfalls sehr einfach ist. Es fügt lediglich die Middleware-Verarbeitungsfunktionen der Parameter durch Anhängen zu seinen eigenen Handlern hinzu.
Im kleinen Beispiel besteht der letzte Schritt darin, die Run-Methode der Engine ohne Parameter aufzurufen. Nach dem Aufruf startet das gesamte Framework und der Besuch der registrierten Adresse mit einem Browser kann den Rückruf korrekt auslösen.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Die Run-Methode führt nur zwei Dinge aus: die Adresse analysieren und den Dienst starten. Hier muss die Adresse eigentlich nur eine Zeichenfolge übergeben, aber um den Effekt zu erzielen, dass sie übergeben oder nicht übergeben werden kann, wird ein variadischer Parameter verwendet. Die Methode „resolveAddress“ verarbeitet die Ergebnisse verschiedener Situationen von addr.
Beim Starten des Dienstes wird die Methode ListenAndServe im Paket net/http der Standardbibliothek verwendet. Diese Methode akzeptiert eine Abhöradresse und eine Variable der Handler-Schnittstelle. Die Definition der Handler-Schnittstelle ist sehr einfach, mit nur einer ServeHTTP-Methode.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Da die Engine ServeHTTP implementiert, wird die Engine selbst hier an die Methode ListenAndServe übergeben. Wenn eine neue Verbindung zum überwachten Port besteht, ist ListenAndServe für das Akzeptieren und Herstellen der Verbindung verantwortlich. Wenn Daten in der Verbindung vorhanden sind, ruft es die ServeHTTP-Methode des Handlers zur Verarbeitung auf.
Das ServeHTTP der Engine ist die Rückruffunktion zur Verarbeitung von Nachrichten. Werfen wir einen Blick auf den Inhalt.
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
Die Callback-Funktion hat zwei Parameter. Der erste ist w, der zum Empfangen der Anfrageantwort verwendet wird. Schreiben Sie die Antwortdaten an w. Das andere ist req, das die Daten dieser Anfrage enthält. Alle für die Weiterverarbeitung erforderlichen Daten können aus req.
entnommen werden
Die ServeHTTP-Methode führt vier Dinge aus. Rufen Sie zunächst einen Kontext aus dem Pool ab, binden Sie dann den Kontext an die Parameter der Rückruffunktion, rufen Sie dann die Methode handleHTTPRequest mit dem Kontext als Parameter auf, um diese Netzwerkanforderung zu verarbeiten, und fügen Sie schließlich den Kontext wieder in den Pool ein.
Schauen wir uns zunächst nur den Kernteil der handleHTTPRequest-Methode an.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Die handleHTTPRequest-Methode erledigt hauptsächlich zwei Dinge. Rufen Sie zunächst die zuvor registrierte Methode aus dem Basisbaum entsprechend der Adresse der Anforderung ab. Hier werden die Handler dem Kontext für diese Verarbeitung zugewiesen und dann die Next-Funktion des Kontexts aufgerufen, um die Methoden in den Handlern auszuführen. Schreiben Sie abschließend die Rückgabedaten dieser Anfrage in das Objekt vom Typ „ResponseWriter“ des Kontexts.
Bei der Verarbeitung einer HTTP-Anfrage befinden sich alle kontextbezogenen Daten in der Kontextvariablen. Der Autor schrieb auch im Kommentar der Kontextstruktur, dass „Kontext der wichtigste Teil von Gin ist“, was seine Bedeutung zeigt.
Wenn wir oben über die ServeHTTP-Methode der Engine sprechen, ist ersichtlich, dass der Kontext nicht direkt erstellt, sondern über die Get-Methode der Poolvariablen der Engine abgerufen wird. Nach der Entnahme wird der Zustand vor der Verwendung zurückgesetzt und nach der Verwendung wieder in den Pool gelegt.
Die Poolvariable der Engine ist vom Typ sync.Pool. Beachten Sie zunächst, dass es sich um einen vom Go-Beamten bereitgestellten Objektpool handelt, der die gleichzeitige Verwendung unterstützt. Sie können ein Objekt über die Get-Methode aus dem Pool abrufen, und Sie können ein Objekt auch über die Put-Methode in den Pool einfügen. Wenn der Pool leer ist und die Get-Methode verwendet wird, erstellt er ein Objekt über seine eigene New-Methode und gibt es zurück.
Diese New-Methode wird in der New-Methode der Engine definiert. Schauen wir uns noch einmal die neue Methode der Engine an.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Aus dem Code ist ersichtlich, dass die Erstellungsmethode des Kontexts die allocateContext-Methode der Engine ist. Die allocateContext-Methode ist kein Geheimnis. Es führt lediglich eine zweistufige Vorabzuweisung der Slice-Längen durch, erstellt dann das Objekt und gibt es zurück.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Die oben erwähnte Next-Methode des Kontexts führt alle Methoden in den Handlern aus. Werfen wir einen Blick auf die Umsetzung.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Obwohl Handler ein Slice sind, wird die Next-Methode nicht einfach als Durchlauf von Handlern implementiert, sondern führt einen Verarbeitungsfortschrittsdatensatzindex ein, der auf 0 initialisiert, zu Beginn der Methode inkrementiert und nach einer Methode erneut inkrementiert wird Die Ausführung ist abgeschlossen.
Das Design von Next hat eine gute Beziehung zu seiner Verwendung, hauptsächlich zur Zusammenarbeit mit einigen Middleware-Funktionen. Wenn beispielsweise während der Ausführung eines bestimmten Handlers eine Panik ausgelöst wird, kann der Fehler mithilfe von „Recover“ in der Middleware abgefangen werden. Anschließend kann Next erneut aufgerufen werden, um mit der Ausführung der nachfolgenden Handler fortzufahren, ohne dass das gesamte Handler-Array aufgrund des Problems beeinträchtigt wird eines Handlers.
Wenn in Gin die Verarbeitungsfunktion einer bestimmten Anfrage eine Panik auslöst, stürzt das gesamte Framework nicht direkt ab. Stattdessen wird eine Fehlermeldung ausgegeben und der Dienst wird weiterhin bereitgestellt. Es ähnelt in gewisser Weise der Art und Weise, wie Lua-Frameworks normalerweise xpcall verwenden, um Nachrichtenverarbeitungsfunktionen auszuführen. Dieser Vorgang ist der in der offiziellen Dokumentation erwähnte Funktionspunkt „Absturzfrei“.
Wie oben erwähnt, wird bei Verwendung von gin.Default zum Erstellen einer Engine die Use-Methode der Engine ausgeführt, um zwei Funktionen zu importieren. Einer davon ist der Rückgabewert der Recovery-Funktion, die ein Wrapper für andere Funktionen ist. Die zuletzt aufgerufene Funktion ist CustomRecoveryWithWriter. Werfen wir einen Blick auf die Implementierung dieser Funktion.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Wir konzentrieren uns hier nicht auf die Details der Fehlerbehandlung, sondern schauen uns nur an, was sie bewirkt. Diese Funktion gibt eine anonyme Funktion zurück. In dieser anonymen Funktion wird mithilfe von defer eine weitere anonyme Funktion registriert. In dieser inneren anonymen Funktion wird die Wiederherstellung verwendet, um die Panik abzufangen, und anschließend wird eine Fehlerbehandlung durchgeführt. Nachdem die Verarbeitung abgeschlossen ist, wird die Next-Methode des Kontexts aufgerufen, sodass die Handler des Kontexts, die ursprünglich nacheinander ausgeführt wurden, weiterhin ausgeführt werden können.
Lassen Sie mich abschließend die beste Plattform für die Bereitstellung von Gin-Diensten vorstellen: Leapcell.
Erfahren Sie mehr in der Dokumentation!
Leapcell Twitter: https://x.com/LeapcellHQ
Das obige ist der detaillierte Inhalt vonEin tiefer Einblick in Gin: Golangs führendes Framework. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!