GNU/Linux >> Linux Esercitazione >  >> Linux

Routing e validazione delle richieste HTTP con gorilla/mux

La libreria di rete Go include http.ServeMux tipo di struttura, che supporta il multiplexing delle richieste HTTP (routing):un server web instrada una richiesta HTTP per una risorsa ospitata, con un URI come /sales4today , a un gestore di codice; il gestore esegue la logica appropriata prima di inviare una risposta HTTP, in genere una pagina HTML. Ecco uno schizzo dell'architettura:

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

In una chiamata a ListenAndServe metodo per avviare un server HTTP

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

un secondo argomento di nil significa che il DefaultServeMux viene utilizzato per l'instradamento delle richieste.

Il gorilla/mux il pacchetto ha un mux.Router digitare in alternativa a DefaultServeMux o un multiplexer di richiesta personalizzato. In ListenAndServe chiamata, un mux.Router l'istanza sostituirà nil come secondo argomento. Cosa rende il mux.Router quindi attraente è mostrato al meglio attraverso un esempio di codice:

1. Un esempio di crud web app

Il crud l'applicazione web (vedi sotto) supporta le quattro operazioni CRUD (Create Read Update Delete), che corrispondono a quattro metodi di richiesta HTTP:POST, GET, PUT e DELETE, rispettivamente. Nel crud app, la risorsa ospitata è un elenco di coppie di cliché, ciascuna un cliché e un cliché in conflitto come questa coppia:

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

È possibile aggiungere nuove coppie di cliché e modificare o eliminare quelle esistenti.

Il crud applicazione 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)
   }
}

Per concentrarsi sull'instradamento e la convalida delle richieste, il crud l'app non utilizza le pagine HTML come risposte alle richieste. Invece, le richieste generano messaggi di risposta in chiaro:un elenco delle coppie di cliché è la risposta a una richiesta GET, la conferma che una nuova coppia di cliché è stata aggiunta all'elenco è una risposta a una richiesta POST e così via. Questa semplificazione rende facile testare l'app, in particolare il gorilla/mux componenti, con un'utilità della riga di comando come curl .

Il gorilla/mux il pacchetto può essere installato da GitHub. Il crud l'app funziona a tempo indeterminato; quindi, dovrebbe essere terminato con un Control-C o equivalente. Il codice per il crud app, insieme a un file README e curl di esempio test, è disponibile sul mio sito web.

2. Richiedi il percorso

Il mux.Router estende il routing in stile REST, che dà uguale peso al metodo HTTP (ad es. GET) e all'URI o al percorso alla fine di un URL (ad es. /cliches ). L'URI funge da sostantivo per il verbo HTTP (metodo). Ad esempio, in una richiesta HTTP una linea di partenza come

GET /cliches

significa ottenere tutte le coppie di cliché , mentre una linea di partenza come

POST /cliches

significa creare una coppia di cliché dai dati nel corpo HTTP .

Nel crud app web, ci sono cinque funzioni che fungono da gestori delle richieste per cinque varianti di una richiesta 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

Ogni funzione accetta due argomenti:un http.ResponseWriter per inviare una risposta al richiedente e un puntatore a un http.Request , che incapsula le informazioni dalla richiesta HTTP sottostante. Il gorilla/mux Il pacchetto semplifica la registrazione di questi gestori di richieste con il server Web e l'esecuzione della convalida basata su espressioni regolari.

Il startServer funzione nel crud app registra i gestori delle richieste. Considera questa coppia di registrazioni, con router come mux.Router esempio:

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

Queste istruzioni significano che una richiesta GET per la singola barra / o /cliches dovrebbe essere indirizzato a ClichesAll funzione, che quindi gestisce la richiesta. Ad esempio, il ricciolo richiesta (con % come prompt della riga di comando)

% curl --request GET localhost:8888/

produce questa risposta:

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.

Le tre coppie di cliché sono i dati iniziali nel crud app.

In questa coppia di dichiarazioni di registrazione

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

l'URI è lo stesso (/cliches ) ma i verbi differiscono:GET nel primo caso e POST nel secondo. Questa registrazione esemplifica il routing in stile REST perché la differenza nei verbi da sola è sufficiente per inviare le richieste a due gestori diversi.

È consentito più di un metodo HTTP in una registrazione, sebbene ciò metta a dura prova lo spirito del routing in stile REST:

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

Le richieste HTTP possono essere instradate su funzionalità oltre al verbo e all'URI. Ad esempio, la registrazione

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

richiede l'accesso HTTPS per una richiesta POST per creare una nuova coppia di cliché. In modo simile, una registrazione potrebbe richiedere che una richiesta disponga di un elemento di intestazione HTTP specificato (ad esempio, una credenziale di autenticazione).

3. Richiedi la convalida

Il gorilla/mux pacchetto adotta un approccio semplice e intuitivo per richiedere la convalida tramite espressioni regolari. Considera questo gestore di richieste per un prendine uno operazione:

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

Questa registrazione esclude richieste HTTP come

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

perché pippo non è un numero decimale. La richiesta genera il familiare codice di stato 404 (non trovato). L'inclusione del modello regex in questa registrazione del gestore garantisce che ClichesOne viene chiamata per gestire una richiesta solo se l'URI della richiesta termina con un valore intero decimale:

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

Come secondo esempio, considera la richiesta

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

Questa richiesta genera un codice di stato 405 (metodo errato) perché /cliches L'URI è registrato, nel crud app, solo per richieste GET e POST. Una richiesta PUT, come una richiesta GET one, deve includere un ID numerico alla fine dell'URI:

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

4. Problemi di concorrenza

Il gorilla/mux router esegue ogni chiamata a un gestore di richieste registrato come una goroutine separata, il che significa che la concorrenza è inserita nel pacchetto. Ad esempio, se sono presenti dieci richieste simultanee come

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

quindi il mux.Router lancia dieci goroutine per eseguire ClichesCreate gestore.

Delle cinque operazioni di richiesta GET all, GET one, POST, PUT e DELETE, le ultime tre modificano la risorsa richiesta, la clichesList condivisa che ospita le coppie di cliché. Di conseguenza, il crud l'app deve garantire una concorrenza sicura coordinando l'accesso a clichesList . In termini diversi ma equivalenti, il crud  l'app deve impedire una race condition su clichesList . In un ambiente di produzione, un sistema di database potrebbe essere utilizzato per memorizzare una risorsa come clichesList , e la concorrenza sicura potrebbe quindi essere gestita tramite transazioni di database.

Il crud l'app adotta l'approccio Go consigliato per la concorrenza sicura:

  • Solo una singola goroutine, il gestore delle risorse iniziato nel crud app startServer funzione, ha accesso a clichesList una volta che il server web inizia ad ascoltare le richieste.
  • I gestori delle richieste come ClichesCreate e ClichesAll invia un (puntatore a) un crudRequest istanza su un canale Go (thread-safe per impostazione predefinita) e solo il gestore risorse legge da questo canale. Il gestore risorse esegue quindi l'operazione richiesta su clichesList .

L'architettura di concorrenza sicura può essere abbozzata come segue:

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

Con questa architettura, nessun blocco esplicito di clichesList è necessario perché solo una goroutine, il gestore delle risorse, accede a clichesList una volta che le richieste CRUD iniziano ad arrivare.

Per mantenere il crud app il più simultaneo possibile, è essenziale avere un'efficiente divisione del lavoro tra i gestori delle richieste, da un lato, e il singolo gestore delle risorse, dall'altro. Ecco, per la revisione, ClichesCreate gestore della richiesta:

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")
}

Più risorse Linux

  • Comandi Linux cheat sheet
  • Cheat sheet sui comandi avanzati di Linux
  • Corso online gratuito:Panoramica tecnica RHEL
  • Cheat sheet della rete Linux
  • Cheat sheet di SELinux
  • Cheat sheet dei comandi comuni di Linux
  • Cosa sono i container Linux?
  • I nostri ultimi articoli su Linux

Il gestore della richiesta ClichesCreate chiama la funzione di utilità getDataFromRequest , che estrae il nuovo cliché e il controcliche dalla richiesta POST. Il ClichesCreate la funzione crea quindi un nuovo ClichePair , imposta due campi e crea un crudRequest da inviare al gestore unico delle risorse. Questa richiesta include un canale di conferma, che il gestore risorse utilizza per restituire le informazioni al gestore della richiesta. Tutto il lavoro di configurazione può essere eseguito senza coinvolgere il gestore delle risorse perché clichesList non è ancora in corso l'accesso.

La completeRequest funzione di utilità chiamata alla fine di ClichesCreate funzione e gli altri gestori delle richieste

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

mette in gioco il gestore delle risorse inserendo un crudRequest nel crudRequests canale:

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
}

Per una richiesta POST, il gestore risorse chiama la funzione di utilità addPair , che cambia la clichesList risorsa:

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"
}

Il gestore risorse chiama funzioni di utilità simili per le altre operazioni CRUD. Vale la pena ripetere che il gestore delle risorse è l'unica goroutine a leggere o scrivere la clichesList una volta che il server web inizia ad accettare le richieste.

Per applicazioni web di qualsiasi tipo, il gorilla/mux Il pacchetto fornisce il routing delle richieste, la convalida delle richieste e i servizi correlati in un'API semplice e intuitiva. Il crud l'app web mette in evidenza le caratteristiche principali del pacchetto. Fai un giro di prova al pacchetto e probabilmente diventerai un acquirente.


Linux
  1. Estrazione e visualizzazione dei dati con awk

  2. Taglia con Lvm e Dm-crypt?

  3. Esecuzione dello script con ". ” E con “fonte”?

  4. strategia di partizionamento e subvol con btrfs

  5. Cos'è lo sport e il dport?

Ubuntu 22.04 apre la porta HTTP 80 e la porta HTTPS 443 con ufw

Come escludere file e directory con Rsync

Come effettuare una richiesta POST con cURL

Come iscriversi e utilizzare una Yubikey con privacyIDEA

Correzione di HTTP Basic:accesso negato e errore di autenticazione irreversibile con GitLab

Installazione e primi passi con Git