La biblioteca de redes Go incluye el http.ServeMux
tipo de estructura, que admite la multiplexación (enrutamiento) de solicitudes HTTP:un servidor web enruta una solicitud HTTP para un recurso alojado, con un URI como /sales4today , a un controlador de código; el controlador realiza la lógica adecuada antes de enviar una respuesta HTTP, normalmente una página HTML. Aquí hay un boceto de la arquitectura:
+------------+ +--------+ +---------+
HTTP request---->| web server |---->| router |---->| handler |
+------------+ +--------+ +---------+
En una llamada al ListenAndServe
método para iniciar un servidor HTTP
http.ListenAndServe(":8888", nil) // args: port & router
un segundo argumento de nil
significa que el DefaultServeMux
se utiliza para el enrutamiento de solicitudes.
El gorilla/mux
el paquete tiene un mux.Router
type como una alternativa al DefaultServeMux
o un multiplexor de solicitud personalizado. En el ListenAndServe
llamada, un mux.Router
instancia reemplazaría nil
como segundo argumento. Qué hace que el mux.Router
por lo tanto, el atractivo se muestra mejor a través de un ejemplo de código:
1. Una aplicación web cruda de muestra
La basura La aplicación web (ver a continuación) admite las cuatro operaciones CRUD (Crear, leer, actualizar y eliminar), que coinciden con cuatro métodos de solicitud HTTP:POST, GET, PUT y DELETE, respectivamente. En la cruda aplicación, el recurso alojado es una lista de pares de clichés, cada uno un cliché y un cliché en conflicto como este par:
Out of sight, out of mind. Absence makes the heart grow fonder.
Se pueden agregar nuevos pares de clichés y los existentes se pueden editar o eliminar.
La basura aplicación 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)
}
}
Para centrarse en el enrutamiento y la validación de solicitudes, el crud La aplicación no utiliza páginas HTML como respuestas a las solicitudes. En cambio, las solicitudes dan como resultado mensajes de respuesta de texto sin formato:una lista de los pares de clichés es la respuesta a una solicitud GET, la confirmación de que se ha agregado un nuevo par de clichés a la lista es una respuesta a una solicitud POST, y así sucesivamente. Esta simplificación facilita la prueba de la aplicación, en particular, el gorilla/mux
componentes, con una utilidad de línea de comandos como curl .
El gorilla/mux
El paquete se puede instalar desde GitHub. La basura la aplicación se ejecuta indefinidamente; por lo tanto, debe terminarse con Control-C o equivalente. El código para el crud aplicación, junto con un LÉAME y un ejemplo de curl pruebas, está disponible en mi sitio web.
2. Solicitar enrutamiento
El mux.Router
amplía el enrutamiento de estilo REST, lo que otorga el mismo peso al método HTTP (p. ej., GET) y al URI o la ruta al final de una URL (p. ej., /cliches ). El URI sirve como sustantivo para el verbo HTTP (método). Por ejemplo, en una solicitud HTTP, una línea de inicio como
GET /cliches
significa obtener todos los pares de clichés , mientras que una línea de inicio como
POST /cliches
significa crear un par de clichés a partir de datos en el cuerpo HTTP .
En la cruda aplicación web, hay cinco funciones que actúan como controladores de solicitudes para cinco variaciones de una solicitud 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
Cada función toma dos argumentos:un http.ResponseWriter
para enviar una respuesta al solicitante y un puntero a http.Request
, que encapsula información de la solicitud HTTP subyacente. El gorilla/mux
El paquete facilita el registro de estos controladores de solicitudes con el servidor web y la validación basada en expresiones regulares.
El startServer
función en el crud app registra los controladores de solicitudes. Considere este par de registros, con router
como mux.Router
instancia:
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
Estas declaraciones significan que una solicitud GET para la barra inclinada única / o /clichés debe enrutarse a ClichesAll
función, que luego maneja la solicitud. Por ejemplo, el rizo solicitud (con % como indicador de la línea de comandos)
% curl --request GET localhost:8888/
produce esta respuesta:
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.
Los tres pares de clichés son los datos iniciales en el crud aplicación.
En este par de declaraciones de registro
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
la URI es la misma (/cliches ) pero los verbos difieren:GET en el primer caso y POST en el segundo. Este registro ejemplifica el enrutamiento de estilo REST porque la diferencia en los verbos solo es suficiente para enviar las solicitudes a dos controladores diferentes.
Se permite más de un método HTTP en un registro, aunque esto pone a prueba el espíritu del enrutamiento al estilo REST:
router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")
Las solicitudes HTTP se pueden enrutar en funciones además del verbo y el URI. Por ejemplo, el registro
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
requiere acceso HTTPS para una solicitud POST para crear un nuevo par de clichés. De manera similar, un registro puede requerir que una solicitud tenga un elemento de encabezado HTTP específico (por ejemplo, una credencial de autenticación).
3. Solicitar validación
El gorilla/mux
El paquete adopta un enfoque fácil e intuitivo para solicitar la validación a través de expresiones regulares. Considere este controlador de solicitudes para obtener uno operación:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")
Este registro descarta solicitudes HTTP como
% curl --request GET localhost:8888/cliches/foo
porque fu no es un número decimal. La solicitud da como resultado el conocido código de estado 404 (No encontrado). Incluir el patrón regex en este registro de controlador garantiza que ClichesOne
se llama a la función para manejar una solicitud solo si el URI de la solicitud termina con un valor entero decimal:
% curl --request GET localhost:8888/cliches/3 # ok
Como segundo ejemplo, considere la solicitud
% curl --request PUT --data "..." localhost:8888/cliches
Esta solicitud da como resultado un código de estado de 405 (Método incorrecto) porque /cliches URI está registrado, en el crud aplicación, solo para solicitudes GET y POST. Una solicitud PUT, como una solicitud GET one, debe incluir una identificación numérica al final del URI:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
4. Problemas de concurrencia
El gorilla/mux
El enrutador ejecuta cada llamada a un controlador de solicitudes registrado como una gorutina separada, lo que significa que la simultaneidad se incluye en el paquete. Por ejemplo, si hay diez solicitudes simultáneas como
% curl --request POST --data "..." localhost:8888/cliches
luego el mux.Router
lanza diez goroutines para ejecutar el ClichesCreate
controlador.
De las cinco operaciones de solicitud GET all, GET one, POST, PUT y DELETE, las tres últimas alteran el recurso solicitado, la clichesList
compartida que alberga los pares de clichés. En consecuencia, el crud la aplicación debe garantizar una concurrencia segura coordinando el acceso a clichesList
. En términos diferentes pero equivalentes, el crud la aplicación debe evitar una condición de carrera en la clichesList
. En un entorno de producción, se puede usar un sistema de base de datos para almacenar un recurso como clichesList
, y la concurrencia segura podría administrarse a través de transacciones de bases de datos.
La basura la aplicación adopta el enfoque Go recomendado para la concurrencia segura:
- Solo una rutina go, el administrador de recursos comenzó en el crud aplicación
startServer
tiene acceso a laclichesList
una vez que el servidor web comienza a escuchar solicitudes. - Los controladores de solicitudes como
ClichesCreate
yClichesAll
enviar un (puntero a) uncrudRequest
instancia a un canal Go (seguro para subprocesos de forma predeterminada), y el administrador de recursos solo lee desde este canal. El administrador de recursos luego realiza la operación solicitada enclichesList
.
La arquitectura de concurrencia segura se puede esbozar de la siguiente manera:
crudRequest read/write
request handlers------------->resource manager------------>clichesList
Con esta arquitectura, no hay bloqueo explícito de clichesList
es necesario porque solo una gorutina, el administrador de recursos, accede a clichesList
una vez que las solicitudes CRUD comiencen a llegar.
Para mantener la crud aplicación lo más concurrente posible, es esencial tener una división eficiente del trabajo entre los manejadores de solicitudes, por un lado, y el administrador de recursos único, por el otro lado. Aquí, para revisión, está el ClichesCreate
controlador de solicitudes:
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")
}
Más recursos de Linux
- Hoja de trucos de los comandos de Linux
- Hoja de trucos de comandos avanzados de Linux
- Curso en línea gratuito:Descripción general técnica de RHEL
- Hoja de trucos de red de Linux
- Hoja de trucos de SELinux
- Hoja de trucos de los comandos comunes de Linux
- ¿Qué son los contenedores de Linux?
- Nuestros últimos artículos sobre Linux
El controlador de solicitudes ClichesCreate
llama a la función de utilidad getDataFromRequest
, que extrae el nuevo cliché y el contracliché de la solicitud POST. Los ClichesCreate
Luego, la función crea un nuevo ClichePair
, establece dos campos y crea una crudRequest
que se enviará al gestor de recursos único. Esta solicitud incluye un canal de confirmación, que el administrador de recursos utiliza para devolver información al controlador de solicitudes. Todo el trabajo de configuración se puede realizar sin involucrar al administrador de recursos porque la clichesList
no se está accediendo todavía.
La completeRequest
función de utilidad llamada al final de ClichesCreate
función y los otros controladores de solicitudes
completeRequest(cr, res, "create") // shown above
pone en juego el administrador de recursos al poner un crudRequest
en crudRequests
canal:
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
}
Para una solicitud POST, el administrador de recursos llama a la función de utilidad addPair
, que cambia la clichesList
recurso:
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"
}
El administrador de recursos llama a funciones de utilidad similares para las otras operaciones CRUD. Vale la pena repetir que el administrador de recursos es la única rutina que lee o escribe la clichesList
una vez que el servidor web comience a aceptar solicitudes.
Para aplicaciones web de cualquier tipo, gorilla/mux
El paquete proporciona enrutamiento de solicitudes, validación de solicitudes y servicios relacionados en una API sencilla e intuitiva. La basura La aplicación web destaca las características principales del paquete. Realice una prueba de manejo del paquete y es probable que sea un comprador.