Maison > développement back-end > Golang > Une plongée approfondie dans le gin : le cadre de référence de Golang

Une plongée approfondie dans le gin : le cadre de référence de Golang

Linda Hamilton
Libérer: 2025-01-01 05:16:12
original
639 Les gens l'ont consulté

A Deep Dive into Gin: Golang

Introduction

A Deep Dive into Gin: Golang

Gin est un framework web HTTP écrit en Go (Golang). Il dispose d'une API de type Martini, mais avec des performances jusqu'à 40 fois plus rapides que Martini. Si vous avez besoin de performances époustouflantes, procurez-vous du Gin.

Le site officiel de Gin se présente comme un framework web avec des « hautes performances » et une « bonne productivité ». Il mentionne également deux autres bibliothèques. Le premier est Martini, qui est également un framework Web et porte le nom d'une liqueur. Gin dit utiliser son API, mais est 40 fois plus rapide. L'utilisation de httprouter est une raison importante pour laquelle il peut être 40 fois plus rapide que Martini.
Parmi les « Fonctionnalités » du site officiel, huit fonctionnalités clés sont répertoriées, et nous verrons progressivement la mise en œuvre de ces fonctionnalités plus tard.

  • Rapide
  • Support middleware
  • Sans crash
  • Validation JSON
  • Regroupement d'itinéraires
  • Gestion des erreurs
  • Rendu intégré/Extensible

Commencez par un petit exemple

Regardons le plus petit exemple donné dans la documentation officielle.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutez cet exemple, puis utilisez un navigateur pour visiter http://localhost:8080/ping, et vous obtiendrez un "pong".
Cet exemple est très simple. Il peut être divisé en seulement trois étapes :

  1. Utilisez gin.Default() pour créer un objet Engine avec des configurations par défaut.
  2. Enregistrez une fonction de rappel pour l'adresse "/ping" dans la méthode GET du Moteur. Cette fonction renverra un "pong".
  3. Démarrez le moteur pour commencer à écouter le port et à fournir des services.

Méthode HTTP

À partir de la méthode GET dans le petit exemple ci-dessus, nous pouvons voir que dans Gin, les méthodes de traitement des méthodes HTTP doivent être enregistrées en utilisant les fonctions correspondantes portant les mêmes noms.
Il existe neuf méthodes HTTP, et les quatre plus couramment utilisées sont GET, POST, PUT et DELETE, qui correspondent respectivement aux quatre fonctions d'interrogation, d'insertion, de mise à jour et de suppression. Il convient de noter que Gin fournit également l'interface Any, qui peut directement lier toutes les méthodes de traitement de la méthode HTTP à une seule adresse.
Le résultat renvoyé contient généralement deux ou trois parties. Le code et le message sont toujours là et les données sont généralement utilisées pour représenter des données supplémentaires. S'il n'y a pas de données supplémentaires à renvoyer, elles peuvent être omises. Dans l'exemple, 200 est la valeur du champ code, et "pong" est la valeur du champ message.

Créer une variable de moteur

Dans l'exemple ci-dessus, gin.Default() a été utilisé pour créer le moteur. Cependant, cette fonction est un wrapper pour New. En fait, le moteur est créé via la nouvelle interface.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Jetez simplement un bref coup d'œil au processus de création pour l'instant et ne vous concentrez pas sur la signification des différentes variables membres dans la structure du moteur. On peut voir qu'en plus de créer et d'initialiser une variable moteur de type Engine, New définit également Engine.pool.New sur une fonction anonyme qui appelle Engine.allocateContext(). La fonction de cette fonction sera discutée plus tard.

Enregistrer les fonctions de rappel d'itinéraire

Il existe une structure RouterGroup intégrée dans le moteur. Les interfaces liées aux méthodes HTTP du Moteur sont toutes héritées de RouterGroup. Le "Regroupement d'itinéraires" dans les points caractéristiques mentionnés sur le site officiel est réalisé via la structure RouterGroup.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Chaque RouterGroup est associé à un chemin de base basePath. Le basePath du RouterGroup intégré dans le moteur est "/".
Il existe également un ensemble de fonctions de traitement Handlers. Toutes les requêtes sous les chemins associés à ce groupe exécuteront en outre les fonctions de traitement de ce groupe, qui sont principalement utilisées pour les appels middleware. Les gestionnaires sont nuls lorsque le moteur est créé et un ensemble de fonctions peut être importé via la méthode Use. Nous verrons cette utilisation plus tard.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode handle de RouterGroup est la dernière entrée pour enregistrer toutes les fonctions de rappel de méthode HTTP. La méthode GET et les autres méthodes liées aux méthodes HTTP appelées dans l'exemple initial ne sont que des wrappers pour la méthode handle.
La méthode handle calculera le chemin absolu en fonction du basePath du RouterGroup et du paramètre de chemin relatif, et appellera en même temps la méthode combineHandlers pour obtenir le tableau final des gestionnaires. Ces résultats sont transmis en paramètres à la méthode addRoute du Moteur pour enregistrer les fonctions de traitement.

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()
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode combineHandlers crée une tranche mergedHandlers, puis y copie les gestionnaires du RouterGroup lui-même, puis y copie les gestionnaires des paramètres et enfin renvoie mergedHandlers. C'est-à-dire que lors de l'enregistrement d'une méthode utilisant handle, le résultat réel inclut les gestionnaires du RouterGroup lui-même.

Utilisez Radix Tree pour accélérer la récupération d'itinéraire

Dans le point de fonctionnalité "Rapide" mentionné sur le site officiel, il est mentionné que le routage des requêtes réseau est implémenté sur la base de l'arbre radix (Radix Tree). Cette partie n'est pas implémentée par Gin, mais par httprouter mentionné dans l'introduction de Gin au début. Gin utilise httprouter pour réaliser cette partie de la fonction. La mise en œuvre de l’arbre de base ne sera pas évoquée ici pour le moment. Nous nous concentrerons uniquement sur son utilisation pour l’instant. Peut-être que nous écrirons plus tard un article séparé sur la mise en œuvre de l'arbre de base.
Dans le moteur, il existe une variable Trees, qui est une tranche de la structure MethodTree. C'est cette variable qui contient les références à tous les arbres de base.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le moteur maintient un arbre de base pour chaque méthode HTTP. Le nœud racine de cet arbre et le nom de la méthode sont enregistrés ensemble dans une variable methodTree, et toutes les variables methodTree sont dans des arbres.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

On voit que dans la méthode addRoute du Moteur, il utilisera d'abord la méthode get des arbres pour obtenir le nœud racine de l'arbre base correspondant à la méthode. Si le nœud racine de l'arbre de base n'est pas obtenu, cela signifie qu'aucune méthode n'a été enregistrée pour cette méthode auparavant, et un nœud d'arbre sera créé en tant que nœud racine de l'arbre et ajouté aux arbres.
Après avoir obtenu le nœud racine, utilisez la méthode addRoute du nœud racine pour enregistrer un ensemble de gestionnaires de fonctions de traitement pour le chemin d'accès. Cette étape consiste à créer un nœud pour le chemin et les gestionnaires et à le stocker dans l'arborescence de base. Si vous essayez d'enregistrer une adresse déjà enregistrée, addRoute générera directement une erreur de panique.
Lors du traitement d'une requête HTTP, il est nécessaire de retrouver la valeur du nœud correspondant via le chemin. Le nœud racine possède une méthode getValue chargée de gérer l'opération de requête. Nous le mentionnerons en parlant du traitement des requêtes HTTP par Gin.

Importer des fonctions de traitement du middleware

La méthode Use de RouterGroup peut importer un ensemble de fonctions de traitement middleware. La « prise en charge du middleware » dans les points de fonctionnalité mentionnés sur le site officiel est obtenue via la méthode d'utilisation.
Dans l'exemple initial, lors de la création de la variable de structure Engine, New n'a pas été utilisé, mais Default a été utilisé. Jetons un coup d'œil à ce que Default fait de plus.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

On voit que c'est une fonction très simple. En plus d'appeler New pour créer l'objet Engine, il appelle uniquement Use pour importer les valeurs de retour de deux fonctions middleware, Logger et Recovery. La valeur de retour de Logger est une fonction de journalisation et la valeur de retour de Recovery est une fonction de gestion de la panique. Nous allons ignorer cela pour l'instant et examiner ces deux fonctions plus tard.
Bien que le moteur intègre RouterGroup, il implémente également la méthode Use, mais il ne s'agit que d'un appel à la méthode Use de RouterGroup et de quelques opérations auxiliaires.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

On peut voir que la méthode Use de RouterGroup est également très simple. Il ajoute simplement les fonctions de traitement middleware des paramètres à ses propres gestionnaires via l'ajout.

Commencez à courir

Dans le petit exemple, la dernière étape consiste à appeler la méthode Run du Moteur sans paramètres. Après l'appel, l'ensemble du framework commence à fonctionner et la visite de l'adresse enregistrée avec un navigateur peut déclencher correctement le rappel.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode Run ne fait que deux choses : analyser l'adresse et démarrer le service. Ici, l'adresse n'a en fait besoin que de transmettre une chaîne, mais afin d'obtenir l'effet de pouvoir passer ou ne pas passer, un paramètre variadique est utilisé. La méthode solveAddress gère les résultats de différentes situations d'addr.
Le démarrage du service utilise la méthode ListenAndServe du package net/http de la bibliothèque standard. Cette méthode accepte une adresse d'écoute et une variable de l'interface Handler. La définition de l'interface Handler est très simple, avec une seule méthode ServeHTTP.

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()
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Étant donné que le moteur implémente ServeHTTP, le moteur lui-même sera transmis ici à la méthode ListenAndServe. Lorsqu'il y aura une nouvelle connexion au port surveillé, ListenAndServe sera responsable de l'acceptation et de l'établissement de la connexion, et lorsqu'il y aura des données sur la connexion, il appellera la méthode ServeHTTP du gestionnaire pour le traitement.

Messages de processus

Le ServeHTTP du Moteur est la fonction de rappel pour le traitement des messages. Jetons un coup d'œil à son contenu.

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
}
Copier après la connexion

La fonction de rappel a deux paramètres. Le premier est w qui est utilisé pour recevoir la réponse à la demande. Écrivez les données de réponse dans w. L'autre est req qui contient les données de cette requête. Toutes les données nécessaires au traitement ultérieur peuvent être lues à partir de req.
La méthode ServeHTTP fait quatre choses. Tout d'abord, récupérez un contexte du pool, puis liez le contexte aux paramètres de la fonction de rappel, puis appelez la méthode handleHTTPRequest avec le contexte comme paramètre pour traiter cette requête réseau, et enfin remettez le contexte dans le pool.
Examinons d'abord uniquement la partie centrale de la méthode handleHTTPRequest.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode handleHTTPRequest fait principalement deux choses. Tout d’abord, récupérez la méthode précédemment enregistrée à partir de l’arbre de base en fonction de l’adresse de la requête. Ici, les gestionnaires seront affectés au Contexte pour ce traitement, puis appelleront la fonction Next du Context pour exécuter les méthodes dans les gestionnaires. Enfin, écrivez les données de retour de cette requête dans l'objet de type réponseWriter du Context.

Contexte

Lors du traitement d'une requête HTTP, toutes les données liées au contexte se trouvent dans la variable Context. L'auteur a également écrit dans le commentaire de la structure Context que "Le contexte est la partie la plus importante du gin", ce qui montre son importance.
En parlant de la méthode ServeHTTP du Moteur ci-dessus, on peut voir que le Contexte n'est pas directement créé, mais obtenu via la méthode Get de la variable pool du Moteur. Après avoir été retiré, son état est réinitialisé avant utilisation, et il est remis dans la piscine après utilisation.
La variable pool du moteur est de type sync.Pool. Pour l'instant, sachez simplement qu'il s'agit d'un pool d'objets fourni par le responsable de Go qui prend en charge une utilisation simultanée. Vous pouvez obtenir un objet du pool via sa méthode Get, et vous pouvez également placer un objet dans le pool à l'aide de la méthode Put. Lorsque le pool est vide et que la méthode Get est utilisée, il créera un objet via sa propre méthode New et le renverra.
Cette méthode New est définie dans la méthode New du Moteur. Jetons un autre regard sur la nouvelle méthode du moteur.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Il ressort du code que la méthode de création du Context est la méthode allocateContext du Engine. Il n’y a aucun mystère dans la méthode allocateContext. Il effectue simplement une pré-allocation en deux étapes des longueurs de tranche, puis crée l'objet et le renvoie.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode Next du Context mentionné ci-dessus exécutera toutes les méthodes des gestionnaires. Jetons un coup d'œil à sa mise en œuvre.

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()
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Bien que les gestionnaires soient une tranche, la méthode Next n'est pas simplement implémentée comme un parcours de gestionnaires, mais introduit un index d'enregistrement de progression du traitement, qui est initialisé à 0, incrémenté au début de la méthode et à nouveau incrémenté après une méthode. l'exécution est terminée.

La conception de Next a une excellente relation avec son utilisation, principalement pour coopérer avec certaines fonctions middleware. Par exemple, lorsqu'une panique est déclenchée lors de l'exécution d'un certain gestionnaire, l'erreur peut être détectée à l'aide de recovery dans le middleware, puis Next peut être rappelé pour continuer à exécuter les gestionnaires suivants sans affecter l'ensemble du tableau des gestionnaires en raison du problème. d'un gestionnaire.

Gérer la panique

Dans Gin, si la fonction de traitement d'une certaine requête déclenche une panique, l'ensemble du framework ne plantera pas directement. Au lieu de cela, un message d'erreur sera généré et le service continuera à être fourni. Cela ressemble quelque peu à la façon dont les frameworks Lua utilisent habituellement xpcall pour exécuter des fonctions de traitement de messages. Cette opération est le point de fonctionnalité "Crash-free" mentionné dans la documentation officielle.
Comme mentionné ci-dessus, lors de l'utilisation de gin.Default pour créer un moteur, la méthode Use du moteur sera exécutée pour importer deux fonctions. L'un d'eux est la valeur de retour de la fonction Recovery, qui est un wrapper d'autres fonctions. La dernière fonction appelée est CustomRecoveryWithWriter. Jetons un coup d'œil à l'implémentation de cette fonction.

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
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous ne nous concentrons pas ici sur les détails de la gestion des erreurs, mais regardons uniquement ce qu'elle fait. Cette fonction renvoie une fonction anonyme. Dans cette fonction anonyme, une autre fonction anonyme est enregistrée en utilisant defer. Dans cette fonction anonyme interne, la récupération est utilisée pour intercepter la panique, puis la gestion des erreurs est effectuée. Une fois le traitement terminé, la méthode Next du Context est appelée, afin que les gestionnaires du Context qui étaient initialement exécutés en séquence puissent continuer à être exécutés.

Leapcell : la plate-forme sans serveur de nouvelle génération pour l'hébergement Web, les tâches asynchrones et Redis

Enfin, permettez-moi de vous présenter la meilleure plateforme pour déployer les services Gin : Leapcell.

A Deep Dive into Gin: Golang

1. Prise en charge multilingue

  • Développez avec JavaScript, Python, Go ou Rust.

2. Déployez gratuitement un nombre illimité de projets

  • payez uniquement pour l'utilisation – pas de demande, pas de frais.

3. Rentabilité imbattable

  • Payez à l'utilisation sans frais d'inactivité.
  • Exemple : 25 $ prend en charge 6,94 millions de requêtes avec un temps de réponse moyen de 60 ms.

4. Expérience de développeur rationalisée

  • Interface utilisateur intuitive pour une configuration sans effort.
  • Pipelines CI/CD entièrement automatisés et intégration GitOps.
  • Mesures et journalisation en temps réel pour des informations exploitables.

5. Évolutivité sans effort et hautes performances

  • Mise à l'échelle automatique pour gérer facilement une concurrence élevée.
  • Zéro frais opérationnels : concentrez-vous uniquement sur la construction.

Explorez-en davantage dans la documentation !

Twitter de Leapcell : https://x.com/LeapcellHQ

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal