En las secciones anteriores, hemos aprendido sobre el flujo de trabajo de la web, y hablamos un poco sobre el paquete http
. En esta sección, vamos a aprender dos funciones básicas que estan en el paquete http
: Conn, ServeMux.
A diferencia de los servidores HTTP normales, Go utiliza goroutine para toda trabajo inicializado por Conn con el fin de lograr una alta concurrencia y rendimiento, por lo que cada trabajo es independiente.
Go usa el siguiente código para esperar a nuevas conexiones de clientes .
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
Como puedes ver, se crea una goroutine para cada conexión , y se pasa el controlador que es capaz de leer los datos de solicitud a la goroutine.
Utilizamos el enrutamiento por defecto en la sección anterior, cuando hablamos de conn.server, el router pasa los datos de solicitud como back-end al controlador.
El struct del router por defecto:
type ServeMux struct {
mu sync.RWMutex //debido a la concurrencia, tenemos que utilizar mutex aquí
m map[string]muxEntry //routers, cada asignación de cadena a un controlador
}
El struct de muxEntry:
type muxEntry struct {
explicit bool // exact match or not
h Handler
}
La interfaz de Handler:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // routing implementer
}
Handler
es una interfaz, pero la función sayhelloName
no implementa su interfaz, entonces ¿cómo podríamos agregarla como controlador? Debido a que hay otro tipo HandlerFunc
en el paquete http
. Nosotros llamamos HandlerFunc
para definir nuestro método sayhelloName
, así sayhelloName
implementa el Handler
al mismo tiempo. Es como si llamaramos HandlerFunc(f)
, y la función f
es forzado a convertirce al tipo HandlerFunc
.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
¿Cómo enrutador llama los controladores después de establecer reglas del router?
El enrutador llama mux.handler.ServeHTTP(w , r)
cuando recibe solicitudes. En otras palabras, se llama la interfaz ServeHTTP
de los controladores.
Ahora, vamos a ver cómo funciona mux.handler
.
func (mux *ServeMux) handler(r *Request) Handler {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
h := mux.match(r.Host + r.URL.Path)
if h == nil {
h = mux.match(r.URL.Path)
}
if h == nil {
h = NotFoundHandler()
}
return h
}
El enrutador utiliza la URL como llave para encontrar el controlador correspondiente que guarda en un mapa, luego llama handler.ServeHTTP para ejecutar funciones y manejar los datos.
En este punto, debes entender el flujo de trabajo del enrutador, y Go realmente apoya routers personalizados. El segundo argumento de ListenAndServe es para la configuración del enrutadores a la medida, entonces cualquier enrutador que implemente la interfaz de Handler
puede ser utilizado.
El siguiente ejemplo muestra cómo implementar un enrutador sencillo.
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
Si no quieres usar un enrutador, todavía puedes lograr lo que escribimos en la sección de arriba reemplazando el segundo argumento de ListenAndServe
a nil, y registrando las URLS usando un HandleFunc
función que recorre todas las URLs registradas para encontrar la mejor coincidencia, entonces debemos preocuparnos por el orden de registro.
código de ejemplo:
http.HandleFunc("/", views.ShowAllTasksFunc)
http.HandleFunc("/complete/", views.CompleteTaskFunc)
http.HandleFunc("/delete/", views.DeleteTaskFunc)
//ShowAllTasksFunc es usado para manejar la URL "/" que tiene por defecto todo
//TODO agregar manejador para 404
func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
context := db.GetTasks("pending") //true cuando tu no quieres eliminar tareas
//db es un paquete que interactua con la base de datos
if message != "" {
context.Message = message
}
homeTemplate.Execute(w, context)
message = ""
} else {
message = "Method not allowed"
http.Redirect(w, r, "/", http.StatusFound)
}
}
Esto está bien para aplicaciones simples las cuales no necesitan ruteos parametrizados, pero ¿cuándo necesitas eso? Puedes usar las herramientas o frameworks, pero como este libro está enfocado en crear aplicaciones web en Go, vamos a enseñarte como manejar ese escenario también.
Cuando la concidencia es hecha, se llama a la función definida en HandleFunc
, así que supongamos que estamos escribiendo un manejador para una lista y queremos eliminar una tarea, así que la aplicación decide que función se va a llamar cuando llegue la petición /delete/1
, entonces registramos la URL de la siguiente manera:
http.HandleFunc("/delete/", views.DeleteTaskFunc)
/delete/1
this URL matches closest with the "/delete/" URL than any other URL so in the r.URL.path
we get the entire URL of the request.
http.HandleFunc("/delete/", views.DeleteTaskFunc)
//DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete
func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
id := r.URL.Path[len("/delete/"):]
if id == "all" {
db.DeleteAll()
http.Redirect(w, r, "/", http.StatusFound)
} else {
id, err := strconv.Atoi(id)
if err != nil {
fmt.Println(err)
} else {
err = db.DeleteTask(id)
if err != nil {
message = "Error deleting task"
} else {
message = "Task deleted"
}
http.Redirect(w, r, "/", http.StatusFound)
}
}
} else {
message = "Method not allowed"
http.Redirect(w, r, "/", http.StatusFound)
}
}
Enlace: https://github.com/thewhitetulip/Tasks/blob/master/views/views.go#L170-#L195
Este método lo que hace básicamente es que la función que maneja la URL `/delete/`, se toma la URL completa, que es `/delete/1`, se toman segmentos de la cadena y se extraen todo lo que después de la cadena de coincidencia, en este caso es `1`. Entonces usamos el paquete `strconv` para convertir la cadena en entero y eliminar la tarea con ese identificador.
En escenarios mas complejos también podemos utilizar este método, la ventaja es que no vamos a tener que usar herramientas de terceros, pero las herramientas de terceros tienden a ser útiles en su sentido propio. Tienes que tomar la decisión de cuál método prefieres. Ninguna respuesta es la respuesta correcta.
Vamos a echar un vistazo a la lista de flujo de ejecución en conjunto.
- Se llama
http.HandleFunc
- Se Llama HandleFunc de DefaultServeMux
- Se Llama Handle de DefaultServeMux
- Se agregan las reglas del enrutamiento a map[string]muxEntry de DefaultServeMux
- Se llama
http.ListenAndServe(":9090" , nil)
- Se instancia el servidor
- Se Llama ListenAndServe del Servidor
- Se Llama net.Listen ( "tcp" , addr ) para escuchar en el puerto.
- Iniciar un bucle, y aceptar las solicitudes en el cuerpo del bucle.
- Instanciar una Conn y se empieza una goroutine para cada solicitud :
go c.serve ()
- Se Lee petición de datos :
w , err : = c.readRequest ()
- Se Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux .
- Se Llama al controlador de ServeHTTP
- Se Ejecuta el código en DefaultServeMux en este caso.
- Elije el controlador URL y ejecutar código del controlador en esta seccion:
mux.handler.ServeHTTP (w , r)
- Cómo elegir handler: A. Normas de router de verificación para esta URL. B. Llamar ServeHTTP en ese controlador, si es que existe. C. Llamar ServeHTTP de NotFoundHandler lo contrario.
- Indice
- Sección anterior: Como trabaja Go con la web
- Siguiente sección: Resumen