GNU/Linux >> LINUX-Kenntnisse >  >> Linux

HTTP-Anfrage-Routing und -Validierung mit Gorilla/Mux

Die Go-Netzwerkbibliothek enthält http.ServeMux Strukturtyp, der HTTP-Anfrage-Multiplexing (Routing) unterstützt:Ein Webserver leitet eine HTTP-Anfrage für eine gehostete Ressource mit einem URI wie /sales4today weiter , zu einem Code-Handler; Der Handler führt die entsprechende Logik aus, bevor er eine HTTP-Antwort sendet, normalerweise eine HTML-Seite. Hier ist eine Skizze der Architektur:

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

In einem Aufruf von ListenAndServe Methode zum Starten eines HTTP-Servers

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

ein zweites Argument von nil bedeutet, dass der DefaultServeMux wird für das Request-Routing verwendet.

Der gorilla/mux Paket hat einen mux.Router Geben Sie als Alternative entweder den DefaultServeMux ein oder ein kundenspezifischer Anforderungsmultiplexer. Im ListenAndServe Aufruf, ein mux.Router Instanz würde nil ersetzen als zweites Argument. Was macht der mux.Router so ansprechend zeigt sich am besten an einem Codebeispiel:

1. Eine Beispiel-Crud-Web-App

Der Schmutz Webanwendung (siehe unten) unterstützt die vier CRUD-Operationen (Create Read Update Delete), die vier HTTP-Anfragemethoden entsprechen:POST, GET, PUT und DELETE. Im Müll app ist die gehostete Ressource eine Liste von Klischeepaaren, jeweils ein Klischee und ein widersprüchliches Klischee wie dieses Paar:

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

Neue Klischeepaare können hinzugefügt und bestehende bearbeitet oder gelöscht werden.

Der Schmutz Web-App

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

Um sich auf das Request-Routing und die Validierung zu konzentrieren, wird das crud app verwendet keine HTML-Seiten als Antworten auf Anfragen. Stattdessen führen Anforderungen zu Klartext-Antwortnachrichten:Eine Liste der Klischeepaare ist die Antwort auf eine GET-Anforderung, eine Bestätigung, dass ein neues Klischeepaar zur Liste hinzugefügt wurde, ist eine Antwort auf eine POST-Anforderung und so weiter. Diese Vereinfachung erleichtert das Testen der App, insbesondere des gorilla/mux Komponenten mit einem Befehlszeilendienstprogramm wie curl .

Der gorilla/mux Paket kann von GitHub installiert werden. Der Schmutz App läuft unbegrenzt; Daher sollte es mit einem Control-C oder Äquivalent abgeschlossen werden. Der Code für das crud app, zusammen mit einer README-Datei und einem Beispiel für curl Tests, ist auf meiner Website verfügbar.

2. Weiterleitung anfordern

Der mux.Router erweitert das Routing im REST-Stil, das der HTTP-Methode (z. B. GET) und dem URI oder Pfad am Ende einer URL (z. B. /cliches) gleiches Gewicht verleiht ). Der URI dient als Substantiv für das HTTP-Verb (Methode). Beispielsweise kann in einer HTTP-Anforderung eine Startzeile wie

GET /cliches

bedeutet alle Klischeepaare erhalten , wohingegen eine Startzeile wie

POST /cliches

bedeutet ein Klischeepaar aus Daten im HTTP-Body erstellen .

Im Müll Web-App gibt es fünf Funktionen, die als Request-Handler für fünf Variationen einer HTTP-Anfrage fungieren:

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

Jede Funktion benötigt zwei Argumente:einen http.ResponseWriter zum Zurücksenden einer Antwort an den Anfragenden und einen Zeiger auf eine http.Request , das Informationen aus der zugrunde liegenden HTTP-Anforderung kapselt. Der gorilla/mux -Paket macht es einfach, diese Request-Handler beim Webserver zu registrieren und eine regex-basierte Validierung durchzuführen.

Der startServer Funktion im Crud app registriert die Request-Handler. Betrachten Sie dieses Registrierungspaar mit router als mux.Router Beispiel:

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

Diese Anweisungen bedeuten, dass eine GET-Anforderung entweder für den einzelnen Schrägstrich / oder /Klischees sollte an ClichesAll weitergeleitet werden Funktion, die dann die Anfrage verarbeitet. Zum Beispiel die Curl Anfrage (mit % als Eingabeaufforderung)

% curl --request GET localhost:8888/

erzeugt diese Antwort:

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.

Die drei Klischeepaare sind die Anfangsdaten im Crud App.

In diesem Paar von Registrierungsanweisungen

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

der URI ist derselbe (/cliches ), aber die Verben unterscheiden sich:GET im ersten Fall und POST im zweiten. Diese Registrierung veranschaulicht Routing im REST-Stil, da allein der Unterschied in den Verben ausreicht, um die Anforderungen an zwei verschiedene Handler zu senden.

In einer Registrierung ist mehr als eine HTTP-Methode erlaubt, obwohl dies den Geist des Routings im REST-Stil strapaziert:

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

HTTP-Anforderungen können neben dem Verb und dem URI auch an Features weitergeleitet werden. Zum Beispiel die Registrierung

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

erfordert HTTPS-Zugriff für eine POST-Anforderung, um ein neues Klischeepaar zu erstellen. In ähnlicher Weise könnte eine Registrierung erfordern, dass eine Anfrage ein bestimmtes HTTP-Header-Element (z. B. einen Authentifizierungsnachweis) hat.

3. Validierung anfordern

Der gorilla/mux -Paket verwendet einen einfachen, intuitiven Ansatz, um die Validierung durch reguläre Ausdrücke anzufordern. Betrachten Sie diesen Anfrage-Handler als einen erhaltenen Betrieb:

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

Diese Registrierung schließt HTTP-Anforderungen wie

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

weil foo ist keine Dezimalzahl. Die Anfrage führt zu dem bekannten Statuscode 404 (Not Found). Das Einfügen des Regex-Musters in diese Handler-Registrierung stellt sicher, dass ClichesOne Die Funktion wird nur aufgerufen, um eine Anfrage zu verarbeiten, wenn der Anfrage-URI mit einem ganzzahligen Dezimalwert endet:

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

Betrachten Sie als zweites Beispiel die Anfrage

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

Diese Anfrage führt zu einem Statuscode von 405 (Bad Method), weil die /cliches URI ist registriert, im crud app, nur für GET- und POST-Anforderungen. Eine PUT-Anfrage muss wie eine GET-Anfrage eine numerische ID am Ende des URI enthalten:

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

4. Parallelitätsprobleme

Der gorilla/mux Router führt jeden Aufruf an einen registrierten Request-Handler als separate Goroutine aus, was bedeutet, dass Parallelität in das Paket eingebrannt ist. Zum Beispiel, wenn es zehn gleichzeitige Anfragen wie

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

dann den mux.Router startet zehn Goroutinen, um ClichesCreate auszuführen Handler.

Von den fünf Anfrageoperationen GET all, GET one, POST, PUT und DELETE ändern die letzten drei die angeforderte Ressource, die gemeinsame clichesList das die Klischeepaare beherbergt. Dementsprechend Grob App muss sichere Parallelität gewährleisten, indem sie den Zugriff auf die clichesList koordiniert . Mit anderen, aber gleichwertigen Begriffen, der Crud  app muss eine Racebedingung auf der clichesList verhindern . In einer Produktionsumgebung kann ein Datenbanksystem verwendet werden, um eine Ressource wie die clichesList zu speichern , und sichere Parallelität könnte dann durch Datenbanktransaktionen verwaltet werden.

Der Schmutz app verwendet den empfohlenen Go-Ansatz für sichere Parallelität:

  • Nur eine einzige Goroutine, der Ressourcenmanager begann im Grobgrund app startServer Funktion, hat Zugriff auf die clichesList sobald der Webserver auf Anfragen wartet.
  • Die Anfrage-Handler wie ClichesCreate und ClichesAll einen (Zeiger auf) einen crudRequest senden Instanz zu einem Go-Kanal (standardmäßig Thread-sicher) und der Ressourcenmanager allein liest aus diesem Kanal. Der Ressourcenmanager führt dann die angeforderte Operation auf der clichesList durch .

Die Safe-Concurrency-Architektur kann wie folgt skizziert werden:

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

Bei dieser Architektur kein explizites Sperren der clichesList wird benötigt, da nur eine Goroutine, der Ressourcenmanager, auf die clichesList zugreift sobald CRUD-Anfragen eingehen.

Um den Mist zu behalten app so simultan wie möglich zu gestalten, ist eine effiziente Arbeitsteilung zwischen den Request-Handlern auf der einen Seite und dem einzelnen Ressourcenmanager auf der anderen Seite unerlässlich. Hier ist zur Überprüfung das ClichesCreate Anfrage-Handler:

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

Weitere Linux-Ressourcen

  • Spickzettel für Linux-Befehle
  • Spickzettel für fortgeschrittene Linux-Befehle
  • Kostenloser Online-Kurs:RHEL Technical Overview
  • Spickzettel für Linux-Netzwerke
  • SELinux-Spickzettel
  • Spickzettel für allgemeine Linux-Befehle
  • Was sind Linux-Container?
  • Unsere neuesten Linux-Artikel

Der Request-Handler ClichesCreate ruft die Hilfsfunktion getDataFromRequest auf , das das neue Klischee und Gegenklischee aus der POST-Anforderung extrahiert. Die ClichesCreate Funktion erstellt dann ein neues ClichePair , legt zwei Felder fest und erstellt eine crudRequest an den einzelnen Ressourcenmanager zu senden. Diese Anforderung enthält einen Bestätigungskanal, den der Ressourcenmanager verwendet, um Informationen an den Anforderungshandler zurückzusenden. Die gesamte Einrichtungsarbeit kann ohne Einbeziehung des Ressourcenmanagers durchgeführt werden, da die clichesList wird noch nicht zugegriffen.

Die completeRequest Hilfsfunktion, die am Ende von ClichesCreate aufgerufen wird -Funktion und die anderen Request-Handler

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

bringt den Ressourcenmanager ins Spiel, indem er eine crudRequest setzt in die crudRequests Kanal:

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
}

Für eine POST-Anforderung ruft der Ressourcenmanager die Hilfsfunktion addPair auf , was die clichesList ändert 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"
}

Der Ressourcenmanager ruft ähnliche Hilfsfunktionen für die anderen CRUD-Operationen auf. Es lohnt sich zu wiederholen, dass der Ressourcenmanager die einzige Goroutine ist, die die clichesList liest oder schreibt sobald der Webserver beginnt, Anfragen zu akzeptieren.

Für Webanwendungen jeglicher Art ist der gorilla/mux Das Paket bietet Anforderungsweiterleitung, Anforderungsvalidierung und zugehörige Dienste in einer unkomplizierten, intuitiven API. Der Schmutz Web-App hebt die Hauptfunktionen des Pakets hervor. Probieren Sie das Paket aus und Sie werden wahrscheinlich ein Käufer sein.


Linux
  1. Daten extrahieren und anzeigen mit awk

  2. Was ist ein cURL-Befehl und wie wird er verwendet?

  3. Trimmen mit Lvm und Dm-Crypt?

  4. Laufendes Skript mit „. “ Und mit „Quelle“?

  5. Was ist Sport und Hafen?

Ubuntu 22.04 öffnet HTTP-Port 80 und HTTPS-Port 443 mit ufw

So schließen Sie Dateien und Verzeichnisse mit Rsync aus

So stellen Sie eine POST-Anfrage mit cURL

So registrieren und verwenden Sie einen Yubikey mit privacyIDEA

Behebung von HTTP Basic:Zugriff verweigert und schwerwiegender Authentifizierungsfehler mit GitLab

Installation und erste Schritte mit Git