Síťová knihovna Go obsahuje http.ServeMux
typ struktury, který podporuje multiplexování požadavků HTTP (směrování):Webový server směruje požadavek HTTP na hostovaný zdroj s URI, jako je /sales4today , na obsluhu kódu; obsluha provede příslušnou logiku před odesláním odpovědi HTTP, obvykle stránky HTML. Zde je náčrt architektury:
+------------+ +--------+ +---------+
HTTP request---->| web server |---->| router |---->| handler |
+------------+ +--------+ +---------+
Při volání ListenAndServe
způsob spuštění serveru HTTP
http.ListenAndServe(":8888", nil) // args: port & router
druhý argument nil
znamená, že DefaultServeMux
se používá pro směrování požadavků.
gorilla/mux
balíček má mux.Router
zadejte jako alternativu k DefaultServeMux
nebo přizpůsobený multiplexor požadavků. V ListenAndServe
volání, mux.Router
instance by nahradila nil
jako druhý argument. Co dělá mux.Router
takže přitažlivost je nejlépe ukázat na příkladu kódu:
1. Ukázková webová aplikace
surový webová aplikace (viz níže) podporuje čtyři operace CRUD (Create Read Update Delete), které odpovídají čtyřem metodám požadavku HTTP:POST, GET, PUT a DELETE. V hrubém aplikace, hostovaný zdroj je seznam párů klišé, z nichž každý je klišé a konfliktní klišé, jako je tento pár:
Out of sight, out of mind. Absence makes the heart grow fonder.
Lze přidávat nové páry klišé a stávající lze upravovat nebo mazat.
surový webová aplikace
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)
}
}
Chcete-li se zaměřit na směrování a ověřování požadavků, crud aplikace nepoužívá stránky HTML jako odpovědi na požadavky. Místo toho mají požadavky za následek zprávy s odpovědí v prostém textu:Seznam párů klišé je odpovědí na požadavek GET, potvrzení, že do seznamu byl přidán nový pár klišé, je odpovědí na požadavek POST atd. Toto zjednodušení usnadňuje testování aplikace, zejména gorilla/mux
komponenty pomocí nástroje příkazového řádku, jako je curl .
gorilla/mux
balíček lze nainstalovat z GitHubu. surový aplikace běží neomezeně dlouho; proto by měl být ukončen Control-C nebo ekvivalentem. Kód pro crud aplikace spolu se souborem README a ukázkou kudrlinky testy, je k dispozici na mých webových stránkách.
2. Žádost o směrování
mux.Router
rozšiřuje směrování ve stylu REST, které dává stejnou váhu metodě HTTP (např. GET) a URI nebo cestě na konci adresy URL (např. /cliches ). URI slouží jako podstatné jméno pro HTTP sloveso (metodu). Například v požadavku HTTP startovací řádek jako
GET /cliches
znamená získat všechny páry klišé , zatímco startovní čára jako
POST /cliches
znamená vytvořit klišé z dat v těle HTTP .
V hrubém webové aplikace, existuje pět funkcí, které fungují jako obsluha požadavků pro pět variant požadavku 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
Každá funkce má dva argumenty:http.ResponseWriter
pro odeslání odpovědi zpět žadateli a ukazatel na http.Request
, který zapouzdřuje informace ze základního požadavku HTTP. gorilla/mux
balíček usnadňuje registraci těchto obslužných programů na webovém serveru a provádění ověření založeného na regulárních výrazech.
startServer
fungovat v crud aplikace registruje obsluhu požadavků. Zvažte tuto dvojici registrací s router
jako mux.Router
instance:
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
Tyto příkazy znamenají, že požadavek GET buď na jedno lomítko / nebo /cliches by měl být směrován do ClichesAll
funkce, která následně požadavek zpracuje. Například vlna request (s % jako příkazový řádek)
% curl --request GET localhost:8888/
vytvoří tuto odpověď:
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.
Tyto tři klišé páry jsou počátečními údaji v hrubém aplikace.
V této dvojici registračních výpisů
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
URI je stejné (/cliches ), ale slovesa se liší:GET v prvním případě a POST ve druhém. Tato registrace je příkladem směrování ve stylu REST, protože samotný rozdíl ve slovesech stačí k odeslání požadavků dvěma různým obslužným rutinám.
V registraci je povolena více než jedna metoda HTTP, i když to narušuje ducha směrování ve stylu REST:
router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")
Požadavky HTTP mohou být směrovány na funkce kromě slovesa a URI. Například registrace
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
vyžaduje přístup HTTPS pro požadavek POST k vytvoření nového páru klišé. Podobným způsobem může registrace vyžadovat požadavek na zadaný prvek záhlaví HTTP (např. ověřovací pověření).
3. Požádat o ověření
gorilla/mux
balíček má snadný a intuitivní přístup k ověření požadavků prostřednictvím regulárních výrazů. Zvažte tento obslužný nástroj pro získání jednoho operace:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")
Tato registrace vylučuje požadavky HTTP, jako je
% curl --request GET localhost:8888/cliches/foo
protože foo není desetinná číslice. Výsledkem požadavku je známý stavový kód 404 (Nenalezeno). Zahrnutí vzoru regulárního výrazu do této registrace ovladače zajistí, že ClichesOne
funkce se volá ke zpracování požadavku pouze v případě, že identifikátor URI požadavku končí desetinnou celočíselnou hodnotou:
% curl --request GET localhost:8888/cliches/3 # ok
Jako druhý příklad zvažte požadavek
% curl --request PUT --data "..." localhost:8888/cliches
Výsledkem tohoto požadavku je stavový kód 405 (Špatná metoda), protože /cliches URI je registrováno v crud aplikace, pouze pro požadavky GET a POST. Požadavek PUT, stejně jako požadavek GET one, musí obsahovat číselné ID na konci URI:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
4. Problémy se souběhem
gorilla/mux
router provádí každé volání registrovaného handleru požadavku jako samostatnou rutinu, což znamená, že souběžnost je zapečena do balíčku. Pokud například existuje deset současných požadavků, jako je
% curl --request POST --data "..." localhost:8888/cliches
pak mux.Router
spustí deset goroutin ke spuštění ClichesCreate
handler.
Z pěti operací požadavku GET all, GET one, POST, PUT a DELETE, poslední tři mění požadovaný zdroj, sdílený clichesList
kde sídlí klišé páry. V souladu s tím surový aplikace musí zaručit bezpečnou souběžnost koordinací přístupu k clichesList
. Jinými, ale ekvivalentními slovy, crud aplikace musí zabránit sporu na clichesList
. V produkčním prostředí lze k uložení zdroje, jako je clichesList
, použít databázový systém a bezpečný souběžný provoz by pak mohl být spravován prostřednictvím databázových transakcí.
surový aplikace využívá doporučený přístup Go k bezpečnému souběžnému běhu:
- Pouze jeden gorutin, správce zdrojů začalo v hrubém aplikace
startServer
má přístup kclichesList
jakmile webový server začne naslouchat požadavkům. - Obsluhy požadavků, jako je
ClichesCreate
aClichesAll
odeslat (ukazatel na)crudRequest
instance do kanálu Go (ve výchozím nastavení bezpečné pro vlákna) a samotný správce prostředků čte z tohoto kanálu. Správce zdrojů poté provede požadovanou operaci naclichesList
.
Architekturu bezpečného souběžného provozu lze načrtnout následovně:
crudRequest read/write
request handlers------------->resource manager------------>clichesList
S touto architekturou žádné explicitní zamykání clichesList
je potřeba, protože pouze jedna rutina, správce zdrojů, přistupuje k clichesList
jakmile začnou přicházet požadavky CRUD.
Chcete-li zachovat surovost pokud možno souběžně, je nezbytné mít efektivní rozdělení práce mezi zpracovatele požadavků na jedné straně a správce jediného zdroje na straně druhé. Zde je pro kontrolu ClichesCreate
obsluha žádosti:
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")
}
Další zdroje pro Linux
- Cheat pro příkazy Linuxu
- Cheat sheet pro pokročilé příkazy systému Linux
- Bezplatný online kurz:Technický přehled RHEL
- Síťový cheat pro Linux
- Cheat sheet SELinux
- Cheat pro běžné příkazy pro Linux
- Co jsou kontejnery systému Linux?
- Naše nejnovější články o Linuxu
Obslužný program požadavku ClichesCreate
volá obslužnou funkci getDataFromRequest
, který extrahuje nové klišé a protiklišé z požadavku POST. ClichesCreate
pak vytvoří nový ClichePair
, nastaví dvě pole a vytvoří crudRequest
k odeslání do správce jediného zdroje. Tato žádost obsahuje potvrzovací kanál, který správce zdrojů používá k vrácení informací zpět obsluze žádosti. Veškerou práci s nastavením lze provést bez zapojení správce zdrojů, protože clichesList
zatím není přístupný.
completeRequest
pomocná funkce volaná na konci ClichesCreate
funkce a další obslužné programy
completeRequest(cr, res, "create") // shown above
přináší do hry správce zdrojů vložením crudRequest
do crudRequests
kanál:
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
}
V případě požadavku POST správce zdrojů zavolá obslužnou funkci addPair
, který změní clichesList
zdroj:
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"
}
Správce prostředků volá podobné pomocné funkce pro ostatní operace CRUD. Stojí za to zopakovat, že správce zdrojů je jedinou rutinou pro čtení nebo zápis clichesList
jakmile webový server začne přijímat požadavky.
Pro webové aplikace jakéhokoli typu, gorilla/mux
balíček poskytuje směrování požadavků, ověřování požadavků a související služby v jednoduchém a intuitivním rozhraní API. surový webová aplikace zdůrazňuje hlavní funkce balíčku. Vyzkoušejte balíček a pravděpodobně se stanete kupujícím.