06. Go web: Cargar plantillas HTML eficientemente en Golang

Publicado por

Hasta ahora, cada vez que el cliente pedía una página a nuestro servidor, la plantilla correspondiente necesitaba ser cargada desde el disco. Esto puede llegar a ser contraproducente si, por ejemplo, muchos clientes realizaran peticiones al mismo tiempo, se necesitarían cargar las mismas plantillas una y otra vez, ocasionando retrasos en los tiempos de respuesta. En esta entrada aprenderemos cómo almacenar las plantillas en memoria para cargarlas solamente una vez, al inicio del programa.

Comenzamos

Haremos uso del, previamente incluido, paquete html/template para cargar las plantillas al principio.

Crearemos una variable tipo *Template (apuntador a Template), tipo de dato incluido con el paquete html/template que nos servirá para almacenar las plantillas. Haremos uso del método template.ParseFiles() de la siguiente forma:

var plantillas, err = template.ParseFiles( "edit.html", "view.html" )

La variable err es necesaria porque el método template.ParseFiles() devuelve dos valores: el apuntador y una variable tipo error; sin almacenar esa segunda variable, nuestro programa no compilaría. Es buena práctica validar los errores de nuestros programas, en entradas posteriores te enseñaré a hacerlo pero por ahora no será necesario.

Cuando intentamos acceder a una plantilla que no existe, ocurre un error fatal y se despliega una serie de códigos de error en consola, pero esto sucede en ejecución y no hay forma de prevenirlo con el código que agregamos anteriormente. Si quieres que tu programa se detenga al inicio de su ejecución cuando no puede cargar una plantilla en lugar de esperar hasta que se haga una petición, puedes utilizar el método template.Must() también incluido en el paquete html/template:

var plantillas = template.Must(template.ParseFiles( "edit.html" , "view.html" ))

Este método envuelve los dos datos que nos regresa template.ParseFiles(). Al momento que detecta que algo falló al cargar las plantillas, pone al programa en estado de «pánico» (panic) e inmediatamente termina su ejecución. Dejamos de utilizar la variable err porque el método template.Must() es el encargado de procesar los errores regresados por template.ParseFiles() y “filtrar” solamente el *Template.

Finalmente, haremos unos cambios a la función cargarPlantilla() para que utilice la variable plantillas. Quedaría de la siguiente manera:

func cargarPlantilla( w http.ResponseWritter, nombre_plantilla string, pagina *Pagina) {
  plantillas.ExecuteTemplate(w, nombre_plantilla + ".html", pagina)
}

Programa completo

El siguiente es el código fuente del servidor con todas las modificaciones que hemos hecho hasta ahora:

package main
import (
  "fmt"
  "io/ioutil"
  "net/http"
  "html/template"
)

type Pagina struct{
  Titulo string
  Cuerpo []byte
}

var plantillas = template.Must(template.ParseFiles("edit.html", "view.html"))

func main(){
  //Creamos y guardamos una página para que el cliente la pida
  pag1 := &Pagina{Titulo: "Ejemplo", Cuerpo:[]byte(
    "¡Hola personita! Este es el cuerpo de tu página.")}
  pag1.guardar()

  http.HandleFunc("/view/", manejadorMostrarPagina)
  http.HandleFunc("/edit/", manejadorEditar)
  http.HandleFunc("/save/", manejadorGuardar)
  fmt.Println("El servidor se encuentra en ejecución.");
  http.ListenAndServe(":8080", nil)
}

//Método para guardar página
func ( p* Pagina ) guardar() error {
  nombre := p.Titulo + ".txt"
  return ioutil.WriteFile( "./view/" + nombre, p.Cuerpo, 0600)
}

//Método para cargar página
func cargarPagina( titulo string ) (*Pagina, error) {
  nombre_archivo := titulo + ".txt"
  fmt.Println("El cliente ha pedido: " + nombre_archivo)
  cuerpo, err := ioutil.ReadFile( "./view/" + nombre_archivo )

  if err != nil {
    return nil, err
  }
  return &Pagina{Titulo: titulo, Cuerpo: cuerpo}, nil
}

//Carga las plantillas HTML
func cargarPlantilla(w http.ResponseWriter, nombre_plantilla string, pagina *Pagina){
  plantillas.ExecuteTemplate(w, nombre_plantilla + ".html", pagina)
}

//Manejador de peticiones
func manejadorMostrarPagina(w http.ResponseWriter, r *http.Request){
  titulo := r.URL.Path[len("/view/"):]
  p, err := cargarPagina(titulo)

  if err != nil {
    http.Redirect(w, r, "/edit/" + titulo, http.StatusFound)
    fmt.Println("La página solicitada no existía. Llamando al editor...")
    return
  }
  cargarPlantilla(w, "view", p)
}

//Manejador para editar wikis
func manejadorEditar(w http.ResponseWriter, r *http.Request){
  titulo := r.URL.Path[len("/edit/"):]
  p, err := cargarPagina(titulo)
  if err != nil{
    p = &Pagina{Titulo: titulo}
  }
  cargarPlantilla(w, "edit", p)
}

//Manejador para guardar wikis
func manejadorGuardar(w http.ResponseWriter, r * http.Request) {
  titulo := r.URL.Path[len("/save/"):]
  cuerpo := r.FormValue("body")
  p := &Pagina{Titulo: titulo, Cuerpo: []byte(cuerpo)}
  fmt.Println("Guardando " + titulo + ".txt...")
  p.guardar()
  http.Redirect(w, r, "/view/" + titulo, http.StatusFound)
}

Después de compilar tu servidor y ponerlo en ejecución, recuerda acceder a la siguiente dirección desde tu navegador para realizar pruebas:

localhost:8080/view/Ejemplo

Finalizando…

Espero que te haya servido lo visto en esta entrada. Cualquier duda puedes dejarla en la sección de comentarios y a la brevedad te ayudaré a resolverla.

En la siguiente entrada aprenderemos a utilizar regex (expresiones regulares) en nuestro servidor para validar las URL. Hasta la próxima. See ya!

6 comments

    1. Hola, gracias por leernos.

      Necesitas agregar el enlace al archivo externo o incluir el estilo directamente en el código fuente de las paginas como lo harías con cualquier archivo .html.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *