GNU/Linux >> Tutoriales Linux >  >> Linux

Enrutamiento y validación de solicitudes HTTP con gorilla/mux

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 la clichesList una vez que el servidor web comienza a escuchar solicitudes.
  • Los controladores de solicitudes como ClichesCreate y ClichesAll enviar un (puntero a) un crudRequest 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 en clichesList .

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.


Linux
  1. Extraer y mostrar datos con awk

  2. ¿Qué es un comando cURL y cómo usarlo?

  3. Encuentra la diferencia con mtime - y +

  4. estrategia de partición y subvol con btrfs

  5. ¿Qué es el deporte y el deporte?

Ubuntu 22.04 abre el puerto HTTP 80 y el puerto HTTPS 443 con ufw

Cómo excluir archivos y directorios con Rsync

Cómo hacer una solicitud POST con cURL

Cómo inscribirse y usar una Yubikey con privacidadIDEA

Corrección de HTTP básico:acceso denegado y error de falla de autenticación fatal con GitLab

Instalación y primeros pasos con Git