Desarrollo de servidores REST en Go. Parte 2: aplicación del enrutador gorilla / mux

Este es el segundo artículo de una serie de artículos sobre el desarrollo de servidores REST en Go. En el primer artículo de esta serie, creamos un servidor simple usando herramientas estándar de Go, y luego refactorizamos el código de generación de datos JSON, llevándolo a una función auxiliar. Esto nos permitió llegar a un código bastante compacto para los manejadores de rutas.



Allí hablamos de un problema con nuestro servidor, que es que la lógica de enrutamiento está dispersa en varios lugares de nuestro programa.







Este es un problema al que se enfrentan todos los que escriben servidores HTTP sin utilizar dependencias. A menos que el servidor, teniendo en cuenta el sistema de sus rutas, no sea un diseño extremadamente minimalista (por ejemplo, estos son algunos servidores especializados que tienen solo una o dos rutas), entonces resulta que el tamaño y la complejidad de la organización de el código del enrutador es algo a lo que los programadores experimentados prestan atención muy rápidamente.



▍ Sistema de enrutamiento mejorado



El primer pensamiento que se le podría ocurrir a alguien que decidió mejorar nuestro servidor podría ser la idea de abstraer su sistema de enrutamiento, quizás usando un conjunto de funciones o un tipo de datos con métodos. Existen muchos enfoques interesantes para resolver este problema, aplicables en cada situación específica. El ecosistema Go tiene muchas bibliotecas de terceros poderosas que se han utilizado con éxito en varios proyectos para implementar las capacidades del enrutador. Recomiendo encarecidamente echar un vistazo a este material, que compara varios enfoques para manejar conjuntos simples de rutas.



Antes de pasar a un ejemplo práctico, recordemos cómo funciona la API de nuestro servidor:



POST   /task/              :       ID
GET    /task/<taskid>      :       ID
GET    /task/              :    
DELETE /task/<taskid>      :     ID
GET    /tag/<tagname>      :       
GET    /due/<yy>/<mm>/<dd> :    ,    

      
      





Para que el sistema de enrutamiento sea más conveniente, podemos hacer lo siguiente:



  1. Puede crear un mecanismo que le permita definir manejadores separados para diferentes métodos de la misma ruta. Por ejemplo, una solicitud POST /task/



    debe ser procesada por un controlador y una solicitud GET /task/



    por otro.
  2. Puede hacer que el controlador de ruta se seleccione en función de un análisis más profundo de las solicitudes de lo que es ahora. Es decir, por ejemplo, con este enfoque, deberíamos poder indicar que un manejador procesa una solicitud /task/



    y otro manejador procesa una solicitud a /task/<taskid>



    con una numérica ID



    .
  3. En este caso, el sistema de procesamiento de rutas debería simplemente extraer el número ID



    de /task/<taskid>



    y pasárselo al manejador de alguna manera conveniente para nosotros.


Escribir su propio enrutador en Go es muy fácil. Esto se debe a que puede organizar su trabajo con controladores HTTP utilizando layout. Pero aquí no complaceré mi deseo de escribir todo yo mismo. En cambio, propongo hablar sobre cómo organizar un sistema de enrutamiento utilizando uno de los enrutadores más populares llamado gorilla / mux .



▍ Servidor de aplicaciones de gestión de tareas con gorilla / mux



El paquete gorilla / mux es uno de los enrutadores HTTP más antiguos y populares para Go. La palabra "mux", de acuerdo con la documentación del paquete, significa "multiplexor de solicitud HTTP" ("mux" tiene el mismo significado en la biblioteca estándar).



Dado que se trata de un paquete destinado a resolver una única tarea altamente especializada, es muy fácil de utilizar. Aquí puede encontrar una variante de nuestro servidor que usa gorilla / mux para el enrutamiento . Aquí está el código para definir las rutas:



router := mux.NewRouter()
router.StrictSlash(true)
server := NewTaskServer()

router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")
router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")
router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")
router.HandleFunc("/task/{id:[0-9]+}/", server.getTaskHandler).Methods("GET")
router.HandleFunc("/task/{id:[0-9]+}/", server.deleteTaskHandler).Methods("DELETE")
router.HandleFunc("/tag/{tag}/", server.tagHandler).Methods("GET")
router.HandleFunc("/due/{year:[0-9]+}/{month:[0-9]+}/{day:[0-9]+}/", server.dueHandler).Methods("GET")

      
      





Tenga en cuenta que estas definiciones por sí solas cierran inmediatamente los dos primeros elementos de la lista anterior de tareas que deben resolverse para mejorar la conveniencia de trabajar con rutas. Debido al hecho de que las llamadas se utilizan en la descripción de rutas Methods



, podemos asignar fácilmente diferentes métodos para diferentes manejadores en una ruta. Hacer coincidir las plantillas (utilizando expresiones regulares) en las formas nos permite distinguir fácilmente /task/



y /task/<taskid>



en la descripción de la ruta de nivel superior.



Para abordar la tarea, que se encuentra en el tercer párrafo de nuestra lista, veamos el uso getTaskHandler



:



func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {
  log.Printf("handling get task at %s\n", req.URL.Path)

  //          Atoi,   
  //   ,    [0-9]+.
  id, _ := strconv.Atoi(mux.Vars(req)["id"])
  ts.Lock()
  task, err := ts.store.GetTask(id)
  ts.Unlock()

  if err != nil {
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  }

  renderJSON(w, task)
}

      
      





En una definición de ruta, una ruta /task/{id:[0-9]+}/



describe una expresión regular utilizada para analizar una ruta y asigna un identificador a una "variable" id



. Se puede acceder a esta "variable" llamando a la función mux.Vars



y pasándola req



(gorilla / mux almacena esta variable en el contexto de cada solicitud, y mux.Vars



es una función auxiliar conveniente para trabajar con ella).



▍ Comparación de diferentes enfoques para organizar el enrutamiento



Así es como se ve la secuencia de lectura de código en la versión original del servidor para aquellos que buscan entender cómo se procesa una ruta GET /task/<taskid>



.





Esto es lo que debe leer si desea comprender el código que usa gorilla / mux:





Cuando use gorilla / mux, no solo tendrá que "saltar" menos a través del texto del programa. Aquí, además, tendrás que leer mucho menos código. En mi humilde opinión, esto es muy bueno en términos de mejorar la legibilidad del código. Describir rutas cuando se usa gorilla / mux es una tarea simple y solo requiere una pequeña cantidad de código para resolver. Y quien lea este código comprenderá inmediatamente cómo funciona este código. Otra ventaja de este enfoque es que todas las rutas se pueden ver literalmente mirando el código en un solo lugar. Y, de hecho, el código de configuración de enrutamiento ahora es muy similar a la descripción de forma libre de nuestra API REST.



Me gusta usar paquetes como gorilla / mux porque son herramientas muy especializadas. Resuelven un solo problema y lo hacen bien. No se “arrastran” por todos los rincones del código del programa del proyecto, lo que significa que, si es necesario, pueden eliminarse fácilmente o reemplazarse por otra cosa. Si miras el código completode la variante de servidor de la que estamos hablando en este artículo, puede ver que el alcance de los mecanismos gorilla / mux se limita a unas pocas líneas de código. Si, a medida que se desarrolla el proyecto, se encuentra alguna limitación en el paquete gorilla / mux que es incompatible con las especificaciones de este proyecto, se debe resolver la tarea de reemplazar gorilla / mux con otro enrutador de terceros (o con su propio enrutador) rápida y fácilmente.



¿Qué enrutador usaría al desarrollar un servidor REST en Go?








All Articles