Là, nous avons parlé d'un problème avec notre serveur, à savoir que la logique de routage est dispersée à plusieurs endroits dans notre programme.
C'est un problème auquel tous ceux qui écrivent des serveurs HTTP sans utiliser de dépendances sont confrontés. A moins que le serveur, compte tenu du système de ses routes, ne soit pas une conception extrêmement minimaliste (par exemple, ce sont des serveurs spécialisés qui n'ont qu'une ou deux routes), alors il s'avère que la taille et la complexité de l'organisation de le code du routeur est quelque chose que les programmeurs expérimentés prêtent très rapidement attention.
▍ Système de routage amélioré
La première pensée qui pourrait venir à l'esprit de quelqu'un qui a décidé d'améliorer notre serveur pourrait être l'idée de faire abstraction de son système de routage, peut-être en utilisant un ensemble de fonctions ou un type de données avec des méthodes. Il existe de nombreuses approches intéressantes pour résoudre ce problème, applicables dans chaque situation spécifique. L'écosystème Go possède de nombreuses bibliothèques tierces puissantes qui ont été utilisées avec succès dans divers projets pour implémenter des capacités de routeur. Je recommande fortement de jeter un œil à ce matériel, qui compare plusieurs approches pour gérer des ensembles simples d'itinéraires.
Avant de passer à un exemple pratique, rappelons comment fonctionne l'API de notre serveur :
POST /task/ : ID GET /task/<taskid> : ID GET /task/ : DELETE /task/<taskid> : ID GET /tag/<tagname> : GET /due/<yy>/<mm>/<dd> : ,
Afin de rendre le système de routage plus pratique, nous pouvons faire ceci :
- Vous pouvez créer un mécanisme qui vous permet de définir des gestionnaires distincts pour différentes méthodes de la même route. Par exemple, une demande
POST /task/
doit être traitée par un gestionnaire et une demandeGET /task/
par un autre. - Vous pouvez faire en sorte que le gestionnaire d'itinéraire soit sélectionné en fonction d'une analyse plus approfondie des demandes qu'il ne l'est actuellement. C'est-à-dire, par exemple, qu'avec cette approche, nous devrions être en mesure d'indiquer qu'un gestionnaire traite une demande à
/task/
, et qu'un autre gestionnaire traite une demande à/task/<taskid>
avec une valeur numériqueID
. - Dans ce cas, le système de traitement des routes devrait simplement extraire le numérique à
ID
partir/task/<taskid>
et de le transmettre au gestionnaire d'une manière pratique pour nous.
L'écriture de votre propre routeur dans Go est très simple. En effet, vous pouvez organiser votre travail avec des gestionnaires HTTP à l'aide de layout. Mais ici, je ne cèderai pas à mon désir de tout écrire moi-même. Au lieu de cela, je propose de parler de la façon d'organiser un système de routage en utilisant l'un des routeurs les plus populaires appelé gorilla / mux .
▍ Serveur d'applications de gestion des tâches utilisant gorilla / mux
Le package gorilla / mux est l'un des routeurs HTTP les plus anciens et les plus populaires pour Go. Le mot "mux", conformément à la documentation du package, signifie "HTTP request multiplexer" ("mux" a la même signification dans la bibliothèque standard).
Puisqu'il s'agit d'un package visant à résoudre une seule tâche hautement spécialisée, il est très facile à utiliser. Une variante de notre serveur qui utilise gorilla/mux pour le routage peut être trouvée ici . Voici le code pour définir les routes :
router := mux.NewRouter()
router.StrictSlash(true)
server := NewTaskServer()
router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")
router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")
router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")
router.HandleFunc("/task/{id:[0-9]+}/", server.getTaskHandler).Methods("GET")
router.HandleFunc("/task/{id:[0-9]+}/", server.deleteTaskHandler).Methods("DELETE")
router.HandleFunc("/tag/{tag}/", server.tagHandler).Methods("GET")
router.HandleFunc("/due/{year:[0-9]+}/{month:[0-9]+}/{day:[0-9]+}/", server.dueHandler).Methods("GET")
Veuillez noter que ces définitions à elles seules ferment immédiatement les deux premiers éléments de la liste ci-dessus des tâches qui doivent être résolues pour améliorer la commodité de travailler avec les itinéraires. En raison du fait que les appels sont utilisés dans la description des routes
Methods
, nous pouvons facilement affecter différentes méthodes pour différents gestionnaires dans une route. La correspondance des modèles (à l'aide d'expressions régulières) dans les manières nous permet de distinguer facilement
/task/
et
/task/<taskid>
au plus haut niveau la description de l'itinéraire.
Afin de traiter la tâche, qui est dans le troisième paragraphe de notre liste, regardons l'utilisation
getTaskHandler
:
func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {
log.Printf("handling get task at %s\n", req.URL.Path)
// Atoi,
// , [0-9]+.
id, _ := strconv.Atoi(mux.Vars(req)["id"])
ts.Lock()
task, err := ts.store.GetTask(id)
ts.Unlock()
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
renderJSON(w, task)
}
Dans une définition de route, une route
/task/{id:[0-9]+}/
décrit une expression régulière utilisée pour analyser un chemin et attribue un identifiant à une "variable"
id
. Cette "variable" est accessible en appelant la fonction
mux.Vars
et en la lui passant
req
(gorilla / mux stocke cette variable dans le contexte de chaque requête, et
mux.Vars
est une fonction d'assistance pratique pour travailler avec elle).
▍ Comparaison de différentes approches pour organiser le routage
Voici à quoi ressemble la séquence de lecture de code dans la version originale du serveur pour ceux qui cherchent à comprendre comment une route est traitée
GET /task/<taskid>
.
Voici ce qu'il faut lire si vous voulez comprendre le code qui utilise gorilla/mux :
Lors de l'utilisation de gorilla / mux, vous n'aurez pas seulement à "sauter" moins à travers le texte du programme. Ici, en plus, vous devrez lire beaucoup moins de code. À mon humble avis, c'est très bien en termes d'amélioration de la lisibilité du code. Décrire les chemins lors de l'utilisation de gorilla / mux est une tâche simple et ne nécessite qu'une petite quantité de code à résoudre. Et quiconque lit ce code comprendra immédiatement comment ce code fonctionne. Un autre avantage de cette approche est que toutes les routes peuvent être vues littéralement en regardant le code à un seul endroit. Et, en fait, le code de configuration du routage ressemble maintenant beaucoup à la description libre de notre API REST.
J'aime utiliser des packages comme gorilla / mux car ce sont des outils très spécialisés. Ils résolvent un seul problème et ils le font bien. Ils ne « rampent » pas dans tous les recoins du code du programme du projet, ce qui signifie que, si nécessaire, ils peuvent être facilement supprimés ou remplacés par autre chose. Si vous regardez le code completde la variante de serveur dont nous parlons dans cet article, vous pouvez voir que la portée des mécanismes gorilla/mux est limitée à quelques lignes de code. Si, au fur et à mesure du développement du projet, une limitation est trouvée dans le package gorilla / mux qui est incompatible avec les spécificités de ce projet, la tâche de remplacer gorilla / mux par un autre routeur tiers (ou avec votre propre routeur) devrait être résolue rapidement et facilement.
Quel routeur utiliseriez-vous pour développer un serveur REST en Go ?