Un récapitulatif du traitement des requêtes dans Go
Le traitement des requêtes HTTP avec Go concerne principalement deux choses: les ServeMuxes et les gestionnaires.
Un ServeMux est essentiellement un routeur de requête HTTP (ou multiplexeur). Il compare les requêtes entrantes à une liste de chemins d’URL prédéfinis et appelle le gestionnaire associé pour le chemin chaque fois qu’une correspondance est trouvée.
Les gestionnaires sont responsables de l’écriture des en-têtes et des corps de réponse. Presque n’importe quel objet peut être un gestionnaire, tant qu’il satisfait l’interface http.Handler
. En termes simples, cela signifie simplement qu’il doit avoir une méthode ServeHTTP
avec la signature suivante :
ServeHTTP(http.ResponseWriter, *http.Request)
Le paquet HTTP de Go est livré avec quelques fonctions pour générer des gestionnaires communs, tels que FileServer
NotFoundHandler
et RedirectHandler
. Commençons par un exemple simple mais artificiel:
Passons rapidement à cela:
- Dans la fonction
main
nous utilisons la fonctionhttp.NewServeMux
pour créer un ServeMux vide. - Nous utilisons ensuite la fonction
http.RedirectHandler
pour créer un nouveau gestionnaire. Ce gestionnaire 307 redirige toutes les requêtes qu’il reçoit vershttp://example.org
. - Ensuite, nous utilisons la fonction
mux.Handle
pour l’enregistrer avec notre nouveau ServeMux, elle agit donc comme gestionnaire de toutes les requêtes entrantes avec le chemin d’URL/foo
. - Enfin, nous créons un nouveau serveur et commençons à écouter les demandes entrantes avec la fonction
http.ListenAndServe
, en passant dans notre ServeMux pour qu’il corresponde aux demandes.
Lancez l’application :
Et visitez http://localhost:3000/foo
dans votre navigateur. Vous devriez constater que votre demande est redirigée avec succès.
Les yeux d’aigle d’entre vous ont peut-être remarqué quelque chose d’intéressant: la signature de la fonction ListenAndServe est ListenAndServe(addr string, handler Handler)
, mais nous avons passé un ServeMux comme deuxième paramètre.
Nous avons pu le faire car le type ServeMux a également une méthode ServeHTTP
, ce qui signifie qu’il satisfait également l’interface du gestionnaire.
Pour moi, cela simplifie les choses de penser à un ServeMux comme étant simplement un type spécial de gestionnaire, qui au lieu de fournir une réponse elle-même transmet la requête à un second gestionnaire. Ce n’est pas autant un saut qu’il en a l’air au premier abord – l’enchaînement des gestionnaires est assez courant dans Go.
Gestionnaires personnalisés
Créons un gestionnaire personnalisé qui répond avec l’heure locale actuelle dans un format donné :
Le code exact ici n’est pas trop important.
Tout ce qui compte vraiment, c’est que nous ayons un objet (dans ce cas, c’est une structure timeHandler
, mais il pourrait également s’agir d’une chaîne ou d’une fonction ou de toute autre chose), et nous avons implémenté une méthode avec la signature ServeHTTP(http.ResponseWriter, *http.Request)
dessus. C’est tout ce dont nous avons besoin pour faire un gestionnaire.
Intégrons ceci dans un exemple concret:
Dans la fonction main
nous avons initialisé la timeHandler
exactement de la même manière que toute structure normale, en utilisant la timeHandler
&
symbole pour générer un pointeur. Et puis, comme dans l’exemple précédent, nous utilisons la fonction mux.Handle
pour l’enregistrer avec notre ServeMux.
Maintenant, lorsque nous exécutons l’application, ServeMux transmettra toute demande de /time
directement à notre méthode timeHandler.ServeHTTP
.
Allez-y et essayez-le: http://localhost:3000/time
.
Notez également que nous pourrions facilement réutiliser le timeHandler
dans plusieurs routes :
Fonctionne en tant que gestionnaires
Pour les cas simples (comme l’exemple ci-dessus), la définition de nouveaux types personnalisés et de méthodes ServeHTTP semble un peu verbeuse. Regardons une approche alternative, où nous exploitons le type http.HandlerFunc
de Go pour contraindre une fonction normale à satisfaire l’interface du gestionnaire.
Toute fonction portant la signature func(http.ResponseWriter, *http.Request)
peut être convertie en un type HandlerFunc. Ceci est utile car les objets HandleFunc sont livrés avec une méthode ServeHTTP
intégrée qui – plutôt intelligemment et commodément – exécute le contenu de la fonction d’origine.
Si cela semble déroutant, essayez de jeter un coup d’œil au code source pertinent. Vous verrez que c’est un moyen très succinct de faire en sorte qu’une fonction satisfasse l’interface du gestionnaire.
Reproduisons l’application timeHandler en utilisant cette technique:
En fait, convertir une fonction en un type HandlerFunc puis l’ajouter à un ServeMux comme celui-ci est si courant que Go fournit un raccourci: la méthode mux.HandleFunc
.
Voici à quoi aurait ressemblé la fonction main()
si nous avions utilisé ce raccourci à la place:
La plupart du temps en utilisant un la fonction de gestionnaire comme celle-ci fonctionne bien. Mais il y a un peu de limitation lorsque les choses commencent à devenir plus complexes.
Vous avez probablement remarqué que, contrairement à la méthode précédente, nous avons dû coder en dur le format de l’heure dans la fonction timeHandler
. Que se passe-t-il lorsque nous voulons transmettre des informations ou des variables de main()
à un gestionnaire ?
Une approche soignée consiste à mettre notre logique de gestionnaire dans une fermeture et à fermer les variables que nous voulons utiliser :
La fonction timeHandler
a maintenant un rôle subtilement différent. Au lieu de contraindre la fonction dans un gestionnaire (comme nous l’avons fait précédemment), nous l’utilisons maintenant pour renvoyer un gestionnaire. Il y a deux éléments clés pour que cela fonctionne.
Tout d’abord, il crée fn
, une fonction anonyme qui accède au tiret &; ou se ferme sur – la variable format
formant une fermeture. Indépendamment de ce que nous faisons avec la fermeture, il pourra toujours accéder aux variables locales de la portée dans laquelle il a été créé – ce qui dans ce cas signifie qu’il aura toujours accès à la variable format
.
Deuxièmement, notre fermeture a la signature func(http.ResponseWriter, *http.Request)
. Comme vous vous en souvenez peut-être plus tôt, cela signifie que nous pouvons le convertir en un type HandlerFunc (afin qu’il satisfasse l’interface du gestionnaire). Notre fonction timeHandler
renvoie ensuite cette fermeture convertie.
Dans cet exemple, nous venons de passer une chaîne simple à un gestionnaire. Mais dans une application du monde réel, vous pouvez utiliser cette méthode pour transmettre une connexion à une base de données, une carte de modèle ou tout autre contexte au niveau de l’application. C’est une bonne alternative à l’utilisation de variables globales, et a l’avantage supplémentaire de créer des gestionnaires autonomes soignés pour les tests.
Vous pouvez également voir ce même modèle écrit comme:
Ou en utilisant une conversion implicite au type HandlerFunc en retour:
Le DefaultServeMux
Vous avez probablement vu DefaultServeMux mentionné dans de nombreux endroits, des exemples les plus simples de Hello World au code source Go.
Il m’a fallu beaucoup de temps pour réaliser que ce n’était pas quelque chose de spécial. Le DefaultServeMux est juste un vieux ServeMux comme nous l’avons déjà utilisé, qui est instancié par défaut lorsque le package HTTP est utilisé. Voici la ligne pertinente de la source Go:
var DefaultServeMux = NewServeMux()
En règle générale, vous ne devez pas utiliser DefaultServeMux car cela pose un risque de sécurité.
Comme le DefaultServeMux est stocké dans une variable globale, n’importe quel paquet peut y accéder et enregistrer une route, y compris les paquets tiers importés par votre application. Si l’un de ces paquets tiers est compromis, ils pourraient utiliser DefaultServeMux pour exposer un gestionnaire malveillant sur le Web.
Donc, en règle générale, c’est une bonne idée d’éviter DefaultServeMux et d’utiliser plutôt votre propre ServeMux à portée locale, comme nous l’avons fait jusqu’à présent. Mais si vous avez décidé de l’utiliser…
Le paquet HTTP fournit quelques raccourcis pour travailler avec DefaultServeMux:http.Handle et http.HandleFunc. Ceux-ci font exactement la même chose que leurs fonctions homonymes que nous avons déjà examinées, à la différence qu’ils ajoutent des gestionnaires au DefaultServeMux au lieu de celui que vous avez créé.
De plus, ListenAndServe reviendra à utiliser DefaultServeMux si aucun autre gestionnaire n’est fourni (c’est-à-dire que le deuxième paramètre est défini sur nil
).
Donc, en dernière étape, mettons à jour notre application timeHandler pour utiliser le DefaultServeMux à la place:
Si vous avez apprécié cet article de blog, n’oubliez pas de consulter mon nouveau livre sur la façon de créer des applications Web professionnelles avec Go!
Suivez-moi sur Twitter @ajmedwards.
Tous les extraits de code de cet article sont libres d’utilisation sous la licence MIT.