Un récapitulatif du traitement des requêtes dans Go

Dernière mise à jour: 21 janvier 2018 Classé dans: tutoriel golang

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 FileServerNotFoundHandler et RedirectHandler. Commençons par un exemple simple mais artificiel:

$ mkdir handler-example$ cd handler-example$ touch main.go

Fichier: main.allez

package mainimport ( "log" "net/http")func main() { mux := http.NewServeMux() rh := http.RedirectHandler("http://example.org", 307) mux.Handle("/foo", rh) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Passons rapidement à cela:

  • Dans la fonction mainnous utilisons la fonction http.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 vers http://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 :

$ go run main.goListening...

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é :

type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}

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:

Fichier: main.go

package mainimport ( "log" "net/http" "time")type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() th := &timeHandler{format: time.RFC1123} mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Dans la fonction mainnous 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 :

func main() { mux := http.NewServeMux() th1123 := &timeHandler{format: time.RFC1123} mux.Handle("/time/rfc1123", th1123) th3339 := &timeHandler{format: time.RFC3339} mux.Handle("/time/rfc3339", th3339) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

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:

Fichier: main.go

package mainimport ( "log" "net/http" "time")func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

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:

func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

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 :

File:main.go

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

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:

func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) })}

Ou en utilisant une conversion implicite au type HandlerFunc en retour:

func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) }}

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:

File:main.go

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { // Note that we skip creating the ServeMux... var format string = time.RFC1123 th := timeHandler(format) // We use http.Handle instead of mux.Handle... http.Handle("/time", th) log.Println("Listening...") // And pass nil as the handler to ListenAndServe. http.ListenAndServe(":3000", nil)}

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.



Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.