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 aclichesList
una volta che il server web inizia ad ascoltare le richieste. - I gestori delle richieste come
ClichesCreate
eClichesAll
invia un (puntatore a) uncrudRequest
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 suclichesList
.
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.