Middlewares pour les applications Web Golang
Lorsque vous travaillez avec des applications Web, telles que des API Web, des sites Web ou toute autre application qui expose un serveur Web et gère les demandes, le comportement le plus courant consiste à gérer chaque URL d’une manière différente. Renvoyez différentes pages Web, conservez les données dans un stockage, récupérez et renvoyez les données au format JSON, entre autres choses.
Cependant, certaines tâches doivent être exécutées quelle que soit la demande que vous recevez de manière générique. Copier et coller le code ou un appel vers une fonction distincte, bien qu’étant une option, n’est pas la meilleure façon d’aborder ce problème. Un problème avec cette approche est la duplication de code, et un autre est l’erreur humaine. Peut-être qu’un développeur oublie d’ajouter l’appel à cette fonction qui recueille des mesures pour une URL donnée ou enregistre des erreurs. Cela causera des maux de tête plus tard. Temps fort. Comme approche alternative à ce problème, nous pouvons utiliser Middlewares.
La principale différence lors de l’utilisation de middlewares est que vous créez un morceau de code générique et que vous l’enregistrez dans le pipeline d’exécution HTTP. De cette façon, vous n’avez pas à vous soucier d’appeler ce code générique dans chaque gestionnaire que vous créez, vous n’aurez pas non plus de duplication de code. Cela semble prometteur, n’est-ce pas?
Ce concept n’est pas unique pour Golang, asp.net utilise exactement le même concept. Ainsi, l’apprentissage de cette technique sera utile non seulement pour les développeurs go, mais pour tous ceux qui travaillent avec des applications web.
Comprendre le pipeline d’exécution HTTP
Dans Golang, la logique de traitement des requêtes HTTP fonctionne comme un pipeline. Dans le scénario le plus simple, vous allez créer une fonction pour chaque URL que vous souhaitez gérer. Dans ces cas, au moins deux choses se produiront:
- L’URL sera extraite de la demande et mise en correspondance avec tous les modèles d’URL que vous avez enregistrés pour gérer;
- La fonction liée au motif apparié sera exécutée;
Voici un exemple simple, chaque demande reçue par ce serveur exécute l’étape 1 ci-dessus, et lorsque l’URL de la demande est /ishealthy
la fonction (anonyme dans ce cas) sera exécutée (étape 2).
C’est bien pour un serveur simple, mais dans une application réelle, vous avez besoin d’un tas de choses génériques avant même que votre fonction ne soit exécutée. Les éléments génériques que vous voudrez peut-être ajouter à toutes les demandes (ou à un groupe de demandes) sont: l’autorisation, la journalisation, la collecte de métriques, la manipulation des en-têtes HTTP, pour n’en nommer que quelques-uns. Une façon de l’implémenter est d’utiliser des middlewares. Le paquet gorrila mux fait une utilisation vraiment explicite de cette technique. C’est pourquoi je vais l’utiliser pour implémenter les exemples. Cependant, vous pouvez créer manuellement des pipelines d’exécution en utilisant uniquement le package standard avec http.Handler
fonctionne aussi si vous le souhaitez.
Middlewares
Le modèle derrière les middlewares et l’ensemble du pipeline d’exécution HTTP est de créer un http.Handler
qui reçoit le gestionnaire suivant comme paramètre. Presque comme dans le style de décorateur. Par conséquent, la signature du MiddlewareFunc
est type MiddlewareFunc func(http.Handler) http.Handler
. Cela signifie qu’à l’intérieur de votre middleware, vous n’avez pas vraiment besoin de savoir quel gestionnaire est le prochain à exécuter, vous ne l’exécutez que lorsque vous avez terminé votre travail. C’est une très bonne abstraction et un isolement des préoccupations. La façon d’enregistrer les middlewares que vous souhaitez utiliser appelle la fonction Use
du routeur.
router := mux.NewRouter()
router.Use(middleware)
L’ordre dans lequel vous enregistrez les middlewares est l’ordre qu’ils vont exécuter (avant d’atteindre le gestionnaire du modèle d’URL). Le code de chaque middleware consiste généralement en une fermeture qui fait quelque chose avec le http.ResponseWriter
et http.Request
qui lui sont passés en tant que paramètres, puis appelez le gestionnaire suivant sur le pipeline. Voici un exemple de middleware simple.
le loggingMiddleware
est l’implémentation du middleware. Comme nous pouvons le voir, ce middleware enregistre l’URL qui a été demandée (ligne 25), exécute le gestionnaire suivant (ligne 26) et enregistre la fin de la demande par la suite. C’est pourquoi cette technique est si puissante, vous avez un contrôle total sur ce que vous voulez exécuter et quand. Dans ce cas, tous les gestionnaires restants sur le pipeline s’exécuteront avant d’atteindre la ligne 27. Le middleware est enregistré sur le main
(ligne 12). Si nous exécutons ce code localement et demandons le /ishealthy
URL, nous voyons quelque chose comme ça sur la console:
2020/05/26 14:56:29 Url requested: /ishealthy
2020/05/26 14:56:29 Returning 200 - Healthy
2020/05/26 14:56:29 Request finished
La signature d’une fonction middleware est très limitée, dans des scénarios réels, vous devrez probablement lui injecter quelques éléments afin de la rendre plus utile, pour cela j’ai écrit un article expliquant une technique très simple mais super puissante pour l’injection de dépendances à emporter.
Middlewares pour sous-routeurs
Une fonctionnalité vraiment intéressante avec gorila mux, est la possibilité de créer des sous-routeurs, basés sur un préfixe de chemin, et d’enregistrer également des middlewares. Cela signifie que vous pouvez avoir un pipeline d’exécution HTTP légèrement différent si vous le souhaitez. En même temps, vous pouvez également partager quelques middlewares communs.
Pour cet exemple, nous avons ajouté deux nouvelles URL /sub/a
et /sub/b
chacun avec sa propre fonction de gestionnaire. Pour gérer ces URL, nous avons ajouté un sous-routeur (ligne 16) et nous avons créé un autre middleware appelé subMiddleware
que vous pouvez trouver à la ligne 41. Ce middleware est enregistré uniquement pour le sous-routeur (ligne 17). Cela signifie que seules les URL gérées par le sous-routeur vont exécuter le subMiddleware
. Cependant, comme loggingMiddleware
a été enregistré sur le routeur principal, le sous-routeur héritera de cette configuration. Appelons ces URL pour voir les différences.
Appel /ishealthy
imprime ce qui suit sur la console:
2020/05/26 16:31:33 Url requested: /ishealthy
2020/05/26 16:31:33 Returning 200 - Healthy
2020/05/26 16:31:33 Request finished
Rien de nouveau ici, l’exécution HTTP n’a pas changé pour cette URL. Cela signifie que le pipeline d’exécution HTTP ressemble à loggingMiddleware
-> handleIsHelthy
.
Appel /sub/a
imprime ceci:
2020/05/26 16:32:42 Url requested: /sub/a
2020/05/26 16:32:42 Another middleware
2020/05/26 16:32:42 Returning 200 - Healthy a
2020/05/26 16:32:42 Request finished
Cette fois, nous pouvons voir que le subMiddleware
a été appelé juste après la loggingMiddleware
. Cela signifie que le pipeline d’exécution HTTP ressemble à loggingMiddleware
-> subMiddleware
-> handleIsHealthyA
.
Et enfin appeler /sub/b
imprime les éléments suivants:
2020/05/26 16:32:50 Url requested: /sub/b
2020/05/26 16:32:50 Another middleware
2020/05/26 16:32:50 Returning 200 - Healthy b
2020/05/26 16:32:50 Request finished
Même comportement qu’avant, le pipeline dans ce cas est loggingMiddleware
-> subMiddleware
-> handleIsHealthyB
.
Nous avons créé deux pipelines d’exécution différents à l’aide de middlewares. C’est très courant dans les cas où nous avons des sections privées et publiques de notre application Web, par exemple. Pour gérer cela, nous pourrions avoir un sous-routeur avec un authorisationMiddleware
encapsulant la logique de gestion des autorisations, toutes très explicites et isolées, et appliquées uniquement à la section privée de notre application. J’espère que cette technique vous sera utile dans vos prochains projets!