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 à laclichesList
une fois que le serveur Web commence à écouter les requêtes. - Les gestionnaires de requêtes tels que
ClichesCreate
etClichesAll
envoyer un (pointeur vers) uncrudRequest
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 laclichesList
.
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.