Un Resumen del Manejo de solicitudes en Go

Última actualización: 21 de enero de 2018 Archivado en: tutorial de golang

Procesar solicitudes HTTP con Go se trata principalmente de dos cosas: ServeMuxes y Manejadores.

Un ServeMux es esencialmente un enrutador de solicitud HTTP (o multiplexor). Compara las solicitudes entrantes con una lista de rutas de URL predefinidas y llama al controlador asociado para la ruta cada vez que se encuentra una coincidencia.Los manejadores

son responsables de escribir encabezados y cuerpos de respuesta. Casi cualquier objeto puede ser un manejador, siempre que satisfaga la interfaz http.Handler. En términos sencillos, eso simplemente significa que debe tener un método ServeHTTP con la siguiente firma:

ServeHTTP(http.ResponseWriter, *http.Request)

El paquete HTTP de Go se envía con algunas funciones para generar controladores comunes, como FileServerNotFoundHandler y RedirectHandler. Comencemos con un ejemplo simple pero artificial:

$ mkdir handler-example$ cd handler-example$ touch main.go

Archivo: principal.ir

package mainimport ( "log" "net/http")func main() { mux := http.NewServeMux() rh := http.RedirectHandler("http://example.org", 307) mux.Handle("/foo", rh) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Veamos esto rápidamente:

  • En la función main utilizamos la función http.NewServeMux para crear un ServeMux vacío.
  • Luego usamos la función http.RedirectHandler para crear un nuevo controlador. Este controlador 307 redirige todas las solicitudes que recibe a http://example.org.
  • A continuación, usamos la función mux.Handle para registrar esto con nuestro nuevo ServeMux, por lo que actúa como el controlador para todas las solicitudes entrantes con la ruta de URL /foo.
  • Finalmente creamos un nuevo servidor y comenzamos a escuchar las solicitudes entrantes con la función http.ListenAndServe, pasando nuestro ServeMux para que coincida con las solicitudes.

Ir adelante y ejecutar la aplicación:

$ go run main.goListening...

Y visita http://localhost:3000/foo en el navegador. Debe encontrar que su solicitud se redirige correctamente.

El ojo de águila de ustedes podría haber notado algo interesante: La firma para la función ListenAndServe es ListenAndServe(addr string, handler Handler), pero pasamos un ServeMux como segundo parámetro.

Pudimos hacer esto porque el tipo ServeMux también tiene un método ServeHTTP, lo que significa que también satisface la interfaz del Controlador.

Para mí, simplifica las cosas pensar en un ServeMux como un tipo especial de manejador, que en lugar de proporcionar una respuesta en sí misma pasa la solicitud a un segundo manejador. Esto no es tanto un salto como suena a primera vista: encadenar a los manipuladores es bastante común en Go.

Controladores Personalizados

Vamos a crear un controlador personalizado que responde con la hora local actual en un formato determinado:

type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}

El código exacto aquí no es demasiado importante.

Todo lo que realmente importa es que tenemos un objeto (en este caso es una estructura timeHandler, pero también podría ser una cadena o función o cualquier otra cosa), y hemos implementado un método con la firma ServeHTTP(http.ResponseWriter, *http.Request) en él. Eso es todo lo que necesitamos para hacer un controlador.

Vamos a incrustar este en un ejemplo concreto:

Archivo: principal.ir

package mainimport ( "log" "net/http" "time")type timeHandler struct { format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() th := &timeHandler{format: time.RFC1123} mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

En la función main inicializamos la función timeHandler exactamente de la misma manera que inicializaríamos cualquier estructura normal, utilizando la función & símbolo para producir un puntero. Y luego, como en el ejemplo anterior, usamos la función mux.Handle para registrar esto con nuestro ServeMux.

Ahora, cuando ejecutemos la aplicación, el ServeMux pasará cualquier solicitud de /time directamente a nuestro método timeHandler.ServeHTTP.

Adelante, inténtalo: http://localhost:3000/time.

Tenga en cuenta también que podríamos reutilizar fácilmente el timeHandler en múltiples rutas:

func main() { mux := http.NewServeMux() th1123 := &timeHandler{format: time.RFC1123} mux.Handle("/time/rfc1123", th1123) th3339 := &timeHandler{format: time.RFC3339} mux.Handle("/time/rfc3339", th3339) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

Funciona como Manejadores

Para casos simples (como el ejemplo anterior) definir nuevos tipos personalizados y métodos ServeHTTP se siente un poco detallado. Veamos un enfoque alternativo, donde aprovechamos el tipo http.HandlerFunc de Go para obligar a una función normal a satisfacer la interfaz del Controlador.

Cualquier función que tenga la firma func(http.ResponseWriter, *http.Request) se puede convertir en un tipo HandlerFunc. Esto es útil porque los objetos HandleFunc vienen con un método incorporado ServeHTTP que, de forma bastante inteligente y conveniente, ejecuta el contenido de la función original.

Si eso suena confuso, intente echar un vistazo al código fuente relevante. Verás que es una forma muy sucinta de hacer que una función satisfaga la interfaz del Controlador.

Reproduzcamos la aplicación del controlador de tiempo utilizando esta técnica:

Archivo: principal.go

package mainimport ( "log" "net/http" "time")func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write(byte("The time is: " + tm))}func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

De hecho, convertir una función a un tipo HandlerFunc y luego agregarla a un ServeMux como este es tan común que Go proporciona un acceso directo: el método mux.HandleFunc.

Esto es lo que la función main() se habría visto si hubiéramos utilizado este acceso directo en su lugar:

func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

La mayor parte del tiempo usando una función como manejador como esta funciona bien. Pero hay una pequeña limitación cuando las cosas comienzan a volverse más complejas.

Probablemente haya notado que, a diferencia del método anterior, hemos tenido que codificar el formato de hora en la función timeHandler. ¿Qué sucede cuando queremos pasar información o variables de main() a un controlador?

Un enfoque ordenado es poner nuestra lógica de controlador en un cierre y cerrar las variables que queremos usar:

Archivo: principal.ir

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}

El timeHandler función tiene ahora una sutil papel diferente. En lugar de obligar a la función a un controlador (como hicimos anteriormente), ahora la estamos usando para devolver un controlador. Hay dos elementos clave para hacer que esto funcione.

Primero crea fn , una función anónima que accede a&guión; o cierra la variable format formando un cierre. Independientemente de lo que hagamos con el cierre, siempre podrá acceder a las variables locales del ámbito en el que se creó, lo que en este caso significa que siempre tendrá acceso a la variable format.

en Segundo lugar nuestro cierre tiene la firma func(http.ResponseWriter, *http.Request). Como recordarás de antes, esto significa que podemos convertirlo en un tipo HandlerFunc (para que satisfaga la interfaz del Controlador). Nuestra función timeHandler devuelve este cierre convertido.

En este ejemplo acabamos de pasar una cadena simple a un controlador. Pero en una aplicación del mundo real, podría usar este método para pasar la conexión a la base de datos, el mapa de plantillas o cualquier otro contexto a nivel de aplicación. Es una buena alternativa al uso de variables globales, y tiene el beneficio adicional de crear manejadores autónomos para pruebas.

también puede ver este mismo patrón se escribe como:

func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) })}

O mediante una conversión implícita a la HandlerFunc tipo de retorno:

func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) }}

El DefaultServeMux

Probablemente haya visto DefaultServeMux mencionado en muchos lugares, desde los ejemplos más simples de Hello World hasta el código fuente de Go.

Me llevó mucho tiempo darme cuenta de que no es nada especial. El DefaultServeMux es solo un ServeMux simple como ya hemos estado usando, que se crea una instancia por defecto cuando se usa el paquete HTTP. Aquí está la línea relevante de la fuente Go:

var DefaultServeMux = NewServeMux()

Por lo general, no debe usar DefaultServeMux porque representa un riesgo de seguridad.

Debido a que el DefaultServeMux se almacena en una variable global, cualquier paquete puede acceder a él y registrar una ruta, incluidos los paquetes de terceros que importe su aplicación. Si uno de esos paquetes de terceros está comprometido, podrían usar DefaultServeMux para exponer a un manejador malicioso a la web.

Por lo tanto, como regla general, es una buena idea evitar el servicio predeterminado y, en su lugar, usar su propio ServeMux de ámbito local, como hemos hecho hasta ahora. Pero si decidiste usarlo…

El paquete HTTP proporciona un par de accesos directos para trabajar con DefaultServeMux: http.Handle y http.HandleFunc. Estos hacen exactamente lo mismo que sus funciones homónimas que ya hemos visto, con la diferencia de que agregan controladores al DefaultServeMux en lugar de uno que usted ha creado.

Además, ListenAndServe volverá a usar el servidor predeterminado si no se proporciona otro controlador (es decir, el segundo parámetro se establece en nil).

Como paso final, vamos a actualizar nuestra aplicación timeHandler para usar el DefaultServeMux en su lugar:

Archivo: main.go

package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write(byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { // Note that we skip creating the ServeMux... var format string = time.RFC1123 th := timeHandler(format) // We use http.Handle instead of mux.Handle... http.Handle("/time", th) log.Println("Listening...") // And pass nil as the handler to ListenAndServe. http.ListenAndServe(":3000", nil)}

Si te ha gustado esta publicación de blog, no olvides echar un vistazo a mi nuevo libro sobre cómo crear aplicaciones web profesionales con Go!

Sígueme en Twitter @ajmedwards.

Todos los fragmentos de código de esta publicación son de uso gratuito bajo la licencia MIT.



Deja una respuesta

Tu dirección de correo electrónico no será publicada.