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
% 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 dieclichesList
sobald der Webserver auf Anfragen wartet. - Die Anfrage-Handler wie
ClichesCreate
undClichesAll
einen (Zeiger auf) einencrudRequest
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 derclichesList
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.