GNU/Linux >> Tutoriels Linux >  >> Linux

Routage et validation des requêtes HTTP avec gorilla/mux

La bibliothèque réseau Go inclut le http.ServeMux type de structure, qui prend en charge le multiplexage des requêtes HTTP (routage) :un serveur Web achemine une requête HTTP pour une ressource hébergée, avec un URI tel que /sales4today , à un gestionnaire de code ; le gestionnaire exécute la logique appropriée avant d'envoyer une réponse HTTP, généralement une page HTML. Voici un croquis de l'architecture :

                 +------------+     +--------+     +---------+
HTTP request---->| web server |---->| router |---->| handler |
                 +------------+     +--------+     +---------+

Dans un appel au ListenAndServe méthode pour démarrer un serveur HTTP

http.ListenAndServe(":8888", nil) // args: port & router

un deuxième argument de nil signifie que le DefaultServeMux est utilisé pour le routage des demandes.

Le gorilla/mux le paquet a un mux.Router tapez comme alternative au DefaultServeMux ou un multiplexeur de requête personnalisé. Dans le ListenAndServe appel, un mux.Router instance remplacerait nil comme deuxième argument. Qu'est-ce qui fait le mux.Router donc attrayant est mieux illustré par un exemple de code :

1. Un exemple d'application Web crud

Le crud L'application Web (voir ci-dessous) prend en charge les quatre opérations CRUD (Create Read Update Delete), qui correspondent à quatre méthodes de requête HTTP :POST, GET, PUT et DELETE, respectivement. Dans le crud app, la ressource hébergée est une liste de paires de clichés, chacune étant un cliché et un cliché en conflit, comme cette paire :

Out of sight, out of mind. Absence makes the heart grow fonder.

De nouvelles paires de clichés peuvent être ajoutées et celles existantes peuvent être modifiées ou supprimées.

Le crud application Web

package main

import (
   "gorilla/mux"
   "net/http"
   "fmt"
   "strconv"
)

const GETALL string = "GETALL"
const GETONE string = "GETONE"
const POST string   = "POST"
const PUT string    = "PUT"
const DELETE string = "DELETE"

type clichePair struct {
   Id      int
   Cliche  string
   Counter string
}

// Message sent to goroutine that accesses the requested resource.
type crudRequest struct {
   verb     string
   cp       *clichePair
   id       int
   cliche   string
   counter  string
   confirm  chan string
}

var clichesList = []*clichePair{}
var masterId = 1
var crudRequests chan *crudRequest

// GET /
// GET /cliches
func ClichesAll(res http.ResponseWriter, req *http.Request) {
   cr := &crudRequest{verb: GETALL, confirm: make(chan string)}
   completeRequest(cr, res, "read all")
}

// GET /cliches/id
func ClichesOne(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "read one")
}

// POST /cliches
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

// PUT /cliches/id
func ClichesEdit(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cliche, counter := getDataFromRequest(req)
   cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)}
   completeRequest(cr, res, "edit")
}

// DELETE /cliches/id
func ClichesDelete(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "delete")
}

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr
   msg := <-cr.confirm
   res.Write([]byte(msg))
   logIt(logMsg)
}

func main() {
   populateClichesList()

   // From now on, this gorountine alone accesses the clichesList.
   crudRequests = make(chan *crudRequest, 8)
   go func() { // resource manager
      for {
         select {
         case req := <-crudRequests:
         if req.verb == GETALL {
            req.confirm<-readAll()
         } else if req.verb == GETONE {
            req.confirm<-readOne(req.id)
         } else if req.verb == POST {
            req.confirm<-addPair(req.cp)
         } else if req.verb == PUT {
            req.confirm<-editPair(req.id, req.cliche, req.counter)
         } else if req.verb == DELETE {
            req.confirm<-deletePair(req.id)
         }
      }
   }()
   startServer()
}

func startServer() {
   router := mux.NewRouter()

   // Dispatch map for CRUD operations.
   router.HandleFunc("/", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

   router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE")

   http.Handle("/", router) // enable the router

   // Start the server.
   port := ":8888"
   fmt.Println("\nListening on port " + port)
   http.ListenAndServe(port, router); // mux.Router now in play
}

// Return entire list to requester.
func readAll() string {
   msg := "\n"
   for _, cliche := range clichesList {
      next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
      msg += next
   }
   return msg
}

// Return specified clichePair to requester.
func readOne(id int) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"

   index := findCliche(id)
   if index >= 0 {
      cliche := clichesList[index]
      msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
   }
   return msg
}

// Create a new clichePair and add to list
func addPair(cp *clichePair) string {
   cp.Id = masterId
   masterId++
   clichesList = append(clichesList, cp)
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

// Edit an existing clichePair
func editPair(id int, cliche string, counter string) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList[index].Cliche = cliche
      clichesList[index].Counter = counter
      msg = "\nCliche edited: " + cliche + " " + counter + "\n"
   }
   return msg
}

// Delete a clichePair
func deletePair(id int) string {
   idStr := strconv.Itoa(id)
   msg := "\n" + "Bad Id: " + idStr + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList = append(clichesList[:index], clichesList[index + 1:]...)
      msg = "\nCliche " + idStr + " deleted\n"
   }
   return msg
}

//*** utility functions
func findCliche(id int) int {
   for i := 0; i < len(clichesList); i++ {
      if id == clichesList[i].Id {
         return i;
      }
   }
   return -1 // not found
}

func getIdFromRequest(req *http.Request) int {
   vars := mux.Vars(req)
   id, _ := strconv.Atoi(vars["id"])
   return id
}

func getDataFromRequest(req *http.Request) (string, string) {
   // Extract the user-provided data for the new clichePair
   req.ParseForm()
   form := req.Form
   cliche := form["cliche"][0]    // 1st and only member of a list
   counter := form["counter"][0]  // ditto
   return cliche, counter
}

func logIt(msg string) {
   fmt.Println(msg)
}

func populateClichesList() {
   var cliches = []string {
      "Out of sight, out of mind.",
      "A penny saved is a penny earned.",
      "He who hesitates is lost.",
   }
   var counterCliches = []string {
      "Absence makes the heart grow fonder.",
      "Penny-wise and dollar-foolish.",
      "Look before you leap.",
   }

   for i := 0; i < len(cliches); i++ {
      cp := new(clichePair)
      cp.Id = masterId
      masterId++
      cp.Cliche = cliches[i]
      cp.Counter = counterCliches[i]
      clichesList = append(clichesList, cp)
   }
}

Pour se concentrer sur le routage et la validation des requêtes, le crud app n'utilise pas les pages HTML comme réponses aux requêtes. Au lieu de cela, les requêtes aboutissent à des messages de réponse en clair :une liste des paires de clichés est la réponse à une requête GET, la confirmation qu'une nouvelle paire de clichés a été ajoutée à la liste est une réponse à une requête POST, et ainsi de suite. Cette simplification permet de tester facilement l'application, en particulier le gorilla/mux composants, avec un utilitaire de ligne de commande tel que curl .

Le gorilla/mux Le package peut être installé à partir de GitHub. Le crud l'application fonctionne indéfiniment ; par conséquent, il doit se terminer par un Control-C ou équivalent. Le code pour le crud app, avec un README et un exemple de curl tests, est disponible sur mon site.

2. Demander le routage

Le mux.Router étend le routage de style REST, qui donne un poids égal à la méthode HTTP (par exemple, GET) et à l'URI ou au chemin à la fin d'une URL (par exemple, /cliches ). L'URI sert de nom pour le verbe HTTP (méthode). Par exemple, dans une requête HTTP, une ligne de départ telle que

GET /cliches

signifie obtenir toutes les paires de clichés , alors qu'une ligne de départ telle que

POST /cliches

signifie créer une paire de clichés à partir des données du corps HTTP .

Dans le crud application Web, cinq fonctions agissent en tant que gestionnaires de requêtes pour cinq variantes d'une requête HTTP :

ClichesAll(...)    # GET: get all of the cliche pairs
ClichesOne(...)    # GET: get a specified cliche pair
ClichesCreate(...) # POST: create a new cliche pair
ClichesEdit(...)   # PUT: edit an existing cliche pair
ClichesDelete(...) # DELETE: delete a specified cliche pair

Chaque fonction prend deux arguments :un http.ResponseWriter pour renvoyer une réponse au demandeur, et un pointeur vers une http.Request , qui encapsule les informations de la requête HTTP sous-jacente. Le gorilla/mux facilite l'enregistrement de ces gestionnaires de requêtes auprès du serveur Web et la validation basée sur les expressions régulières.

Le startServer fonction dans le crud app enregistre les gestionnaires de requêtes. Considérez cette paire d'enregistrements, avec router en tant que mux.Router instance :

router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")

Ces instructions signifient qu'une requête GET pour la seule barre oblique / ou /clichés doit être acheminé vers le ClichesAll fonction, qui gère ensuite la demande. Par exemple, la boucle request (avec % comme invite de ligne de commande)

% curl --request GET localhost:8888/

produit cette réponse :

1: Out of sight, out of mind.  Absence makes the heart grow fonder.
2: A penny saved is a penny earned.  Penny-wise and dollar-foolish.
3: He who hesitates is lost.  Look before you leap.

Les trois paires de clichés sont les données initiales dans le crud application.

Dans cette paire de relevés d'inscription

router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")

l'URI est le même (/cliches ) mais les verbes diffèrent :GET dans le premier cas, et POST dans le second. Cet enregistrement illustre le routage de style REST, car la seule différence dans les verbes suffit à envoyer les requêtes à deux gestionnaires différents.

Plusieurs méthodes HTTP sont autorisées dans un enregistrement, bien que cela contrarie l'esprit du routage de style REST :

router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")

Les requêtes HTTP peuvent être routées sur des fonctionnalités autres que le verbe et l'URI. Par exemple, l'inscription

router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")

nécessite un accès HTTPS pour une requête POST afin de créer une nouvelle paire de clichés. De la même manière, un enregistrement peut nécessiter une demande pour avoir un élément d'en-tête HTTP spécifié (par exemple, un identifiant d'authentification).

3. Demande de validation

Le gorilla/mux package adopte une approche simple et intuitive pour demander la validation via des expressions régulières. Considérez ce gestionnaire de requêtes pour un get one opération :

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

Cet enregistrement exclut les requêtes HTTP telles que

% curl --request GET localhost:8888/cliches/foo

parce que foo n'est pas un nombre décimal. La requête génère le code d'état familier 404 (introuvable). L'inclusion du modèle regex dans cet enregistrement de gestionnaire garantit que le ClichesOne la fonction est appelée pour gérer une requête uniquement si l'URI de la requête se termine par une valeur entière décimale :

% curl --request GET localhost:8888/cliches/3  # ok

Comme deuxième exemple, considérons la requête

% curl --request PUT --data "..." localhost:8888/cliches

Cette requête se traduit par un code d'état de 405 (Bad Method) car le /cliches L'URI est enregistré, dans le crud app, uniquement pour les requêtes GET et POST. Une requête PUT, comme une requête GET one, doit inclure un identifiant numérique à la fin de l'URI :

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")

4. Problèmes de simultanéité

Le gorilla/mux Le routeur exécute chaque appel à un gestionnaire de requêtes enregistré en tant que goroutine distincte, ce qui signifie que la simultanéité est intégrée au package. Par exemple, s'il y a dix requêtes simultanées telles que

% curl --request POST --data "..." localhost:8888/cliches

puis le mux.Router lance dix goroutines pour exécuter le ClichesCreate gestionnaire.

Sur les cinq opérations de requête GET all, GET one, POST, PUT et DELETE, les trois dernières modifient la ressource demandée, la clichesList partagée qui abrite les paires de clichés. En conséquence, le crud l'application doit garantir une concurrence sécurisée en coordonnant l'accès à la clichesList . En termes différents mais équivalents, le crud  l'application doit empêcher une condition de concurrence sur la clichesList . Dans un environnement de production, un système de base de données peut être utilisé pour stocker une ressource telle que clichesList , et la concurrence sécurisée pourrait alors être gérée via des transactions de base de données.

Le crud l'application adopte l'approche Go recommandée pour une simultanéité sécurisée :

  • Une seule goroutine, le gestionnaire de ressources commencé dans le crud application startServer fonction, a accès à la clichesList une fois que le serveur Web commence à écouter les requêtes.
  • Les gestionnaires de requêtes tels que ClichesCreate et ClichesAll envoyer un (pointeur vers) un crudRequest instance à un canal Go (thread-safe par défaut), et seul le gestionnaire de ressources lit à partir de ce canal. Le gestionnaire de ressources effectue alors l'opération demandée sur la clichesList .

L'architecture de sécurité simultanée peut être esquissée comme suit :

                 crudRequest                   read/write
request handlers------------->resource manager------------>clichesList

Avec cette architecture, pas de verrouillage explicite de la clichesList est nécessaire car une seule goroutine, le gestionnaire de ressources, accède à la clichesList une fois que les requêtes CRUD commencent à arriver.

Pour garder le crud app aussi concurrent que possible, il est essentiel d'avoir une division efficace du travail entre les gestionnaires de requêtes, d'un côté, et le gestionnaire de ressources unique, de l'autre côté. Voici, pour examen, le ClichesCreate gestionnaire de requête :

func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

Plus de ressources Linux

  • Aide-mémoire des commandes Linux
  • Aide-mémoire des commandes Linux avancées
  • Cours en ligne gratuit :Présentation technique de RHEL
  • Aide-mémoire sur le réseau Linux
  • Aide-mémoire SELinux
  • Aide-mémoire sur les commandes courantes de Linux
  • Que sont les conteneurs Linux ?
  • Nos derniers articles Linux

Le gestionnaire de requêtes ClichesCreate appelle la fonction utilitaire getDataFromRequest , qui extrait le nouveau cliché et le contre-cliché de la requête POST. Le ClichesCreate la fonction crée alors un nouveau ClichePair , définit deux champs et crée un crudRequest à transmettre au gestionnaire de ressource unique. Cette demande inclut un canal de confirmation, que le gestionnaire de ressources utilise pour renvoyer des informations au gestionnaire de demandes. Tout le travail de configuration peut être effectué sans impliquer le gestionnaire de ressources car la clichesList n'est pas encore accessible.

La completeRequest fonction utilitaire appelée à la fin du ClichesCreate fonction et les autres gestionnaires de requêtes

completeRequest(cr, res, "create") // shown above

met en jeu le gestionnaire de ressources en mettant un crudRequest dans les crudRequests chaîne :

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // send request to resource manager
   msg := <-cr.confirm       // await confirmation string
   res.Write([]byte(msg))    // send confirmation back to requester
   logIt(logMsg)             // print to the standard output
}

Pour une requête POST, le gestionnaire de ressources appelle la fonction utilitaire addPair , qui modifie la clichesList ressource :

func addPair(cp *clichePair) string {
   cp.Id = masterId  // assign a unique ID
   masterId++        // update the ID counter
   clichesList = append(clichesList, cp) // update the list
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

Le gestionnaire de ressources appelle des fonctions utilitaires similaires pour les autres opérations CRUD. Il vaut la peine de répéter que le gestionnaire de ressources est la seule goroutine à lire ou écrire la clichesList une fois que le serveur Web commence à accepter les requêtes.

Pour les applications Web de tout type, le gorilla/mux Le package fournit le routage des demandes, la validation des demandes et les services associés dans une API simple et intuitive. Le crud L'application Web met en évidence les principales fonctionnalités du package. Faites un essai routier du package et vous serez probablement un acheteur.


Linux
  1. Extraire et afficher des données avec awk

  2. Qu'est-ce qu'une commande cURL et comment l'utiliser ?

  3. Couper avec Lvm et Dm-crypt ?

  4. Exécution du script avec ". » Et avec « source » ?

  5. stratégie de partitionnement et subvol avec btrfs

Ubuntu 22.04 ouvre le port HTTP 80 et le port HTTPS 443 avec ufw

Comment exclure des fichiers et des répertoires avec Rsync

Comment faire une requête POST avec cURL

Comment enregistrer et utiliser une Yubikey avec privacyIDEA

Correction de HTTP Basic :accès refusé et erreur d'échec d'authentification fatale avec GitLab

Installation et premiers pas avec Git