Manejar sesiones con Firestore

En este instructivo, se muestra cómo manejar sesiones en Cloud Run.

En muchas apps, se necesita controlar las sesiones para la autenticación y las preferencias del usuario. El paquete sessions del kit de herramientas web Gorilla incluye una implementación basada en un sistema de archivos para realizar esta función. Sin embargo, esta implementación no es adecuada para las apps que se pueden entregar desde varias instancias, ya que la sesión que se registra en una instancia puede diferir de las que se registran en otras. Además, el paquete gorilla/sessions incluye una implementación basada en cookies. Sin embargo, esta implementación requiere que se encripten cookies y se almacene toda la sesión en el cliente, en lugar de solo un ID de sesión, que puede ser muy largo para algunas apps.

Objetivos

  • Escribe la aplicación.
  • Ejecuta la aplicación de manera local.
  • Implementa la aplicación en Cloud Run.

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios.

Es posible que los usuarios de Google Cloud nuevos cumplan con los requisitos para acceder a una prueba gratuita.

Cuando completes las tareas que se describen en este documento, podrás borrar los recursos que creaste para evitar que se te siga facturando. Para obtener más información, consulta Realiza una limpieza.

Antes de comenzar

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Firestore API.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the API

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Firestore API.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the API

  8. En la Google Cloud consola, abre la app en Cloud Shell.

    Ir a Cloud Shell

    Cloud Shell brinda acceso de línea de comandos a los recursos en la nube directamente desde el navegador. Abre Cloud Shell en el navegador y haz clic en Continuar para descargar el código de muestra y pasar al directorio de la app.

  9. En Cloud Shell, configura gcloud CLI para usar tu proyecto Google Cloud nuevo:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID
    

Prepara el proyecto

  1. En la ventana de la terminal, clona el repositorio de la app de muestra en la máquina local:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
  2. Ve al directorio que contiene el código de muestra:

    cd golang-samples/getting-started/sessions

Comprende cómo funciona la app web

Esta app muestra saludos en diferentes idiomas para cada usuario. A los usuarios recurrentes se los saluda siempre en el mismo idioma.

Varias ventanas de la app en las que se muestra un saludo en diferentes idiomas

Para que la app pueda guardar las preferencias de un usuario, debes almacenar la información sobre el usuario actual en una sesión. Esta app de muestra usa Firestore para almacenar los datos de las sesiones.

  1. Primero, la app importa las dependencias y define el tipo de app para conservar un sessions.Store y una plantilla HTML, y determina la lista de saludos.

    import (
    	"context"
    	"html/template"
    	"log"
    	"math/rand"
    	"net/http"
    	"os"
    
    	"cloud.google.com/go/firestore"
    )
    
    // app stores a sessions.Store. Create a new app with newApp.
    type app struct {
    	tmpl         *template.Template
    	collectionID string
    	projectID    string
    }
    
    // session stores the client's session information.
    // This type is also used for executing the template.
    type session struct {
    	Greetings string `json:"greeting"`
    	Views     int    `json:"views"`
    }
    
    // greetings are the random greetings that will be assigned to sessions.
    var greetings = []string{
    	"Hello World",
    	"Hallo Welt",
    	"Ciao Mondo",
    	"Salut le Monde",
    	"Hola Mundo",
    }
    
  2. A continuación, la app define una función main, que crea una nueva instancia de app, registra el controlador de índices y, luego, inicia el servidor HTTP. La función newApp crea la instancia de app estableciendo los valores de projectID y collectionID, y analiza la plantilla HTML.

    func main() {
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
    	if projectID == "" {
    		log.Fatal("GOOGLE_CLOUD_PROJECT must be set")
    	}
    
    	// collectionID is a non-empty identifier for this app, it is used as the Firestore
    	// collection name that stores the sessions.
    	//
    	// Set it to something more descriptive for your app.
    	collectionID := "hello-views"
    
    	a, err := newApp(projectID, collectionID)
    	if err != nil {
    		log.Fatalf("newApp: %v", err)
    	}
    
    	http.HandleFunc("/", a.index)
    
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // newApp creates a new app.
    func newApp(projectID, collectionID string) (app, error) {
    	tmpl, err := template.New("Index").Parse(`<body>{{.Views}} {{if eq .Views 1}}view{{else}}views{{end}} for "{{.Greetings}}"</body>`)
    	if err != nil {
    		log.Fatalf("template.New: %v", err)
    	}
    
    	return app{
    		tmpl:         tmpl,
    		collectionID: collectionID,
    		projectID:    projectID,
    	}, nil
    }
    
  3. El controlador de índices obtiene la sesión del usuario y, si es necesario, crea una. A las sesiones nuevas se les asigna un idioma aleatorio y un recuento de vistas de 0. Luego el recuento de vistas aumenta a uno, la sesión se guarda, y la plantilla HTML escribe la respuesta.

    
    // index uses sessions to assign users a random greeting and keep track of
    // views.
    func (a *app) index(w http.ResponseWriter, r *http.Request) {
    	// Allows requests only for the root path ("/") to prevent duplicate calls.
    	if r.RequestURI != "/" {
    		http.NotFound(w, r)
    		return
    	}
    
    	var session session
    	var doc *firestore.DocumentRef
    
    	isNewSession := false
    
    	ctx := context.Background()
    
    	client, err := firestore.NewClient(ctx, a.projectID)
    	if err != nil {
    		log.Fatalf("firestore.NewClient: %v", err)
    	}
    	defer client.Close()
    
    	// cookieName is a non-empty identifier for this app, it is used as the key name
    	// that contains the session's id value.
    	//
    	// Set it to something more descriptive for your app.
    	cookieName := "session_id"
    
    	// If err is different to nil, it means the cookie has not been set, so it will be created.
    	cookie, err := r.Cookie(cookieName)
    	if err != nil {
    		// isNewSession flag is set to true
    		isNewSession = true
    	}
    
    	// If isNewSession flag is true, the session will be created
    	if isNewSession {
    		// Get unique id for new document
    		doc = client.Collection(a.collectionID).NewDoc()
    
    		session.Greetings = greetings[rand.Intn(len(greetings))]
    		session.Views = 1
    
    		// Cookie is set
    		cookie = &http.Cookie{
    			Name:  cookieName,
    			Value: doc.ID,
    		}
    		http.SetCookie(w, cookie)
    	} else {
    		// The session exists
    
    		// Retrieve document from collection by ID
    		docSnapshot, err := client.Collection(a.collectionID).Doc(cookie.Value).Get(ctx)
    		if err != nil {
    			log.Printf("doc.Get error: %v", err)
    			http.Error(w, "Error getting session", http.StatusInternalServerError)
    			return
    		}
    
    		// Unmarshal documents's content to local type
    		err = docSnapshot.DataTo(&session)
    		if err != nil {
    			log.Printf("doc.DataTo error: %v", err)
    			http.Error(w, "Error parsing session", http.StatusInternalServerError)
    			return
    		}
    
    		doc = docSnapshot.Ref
    
    		// Add 1 to current views value
    		session.Views++
    	}
    
    	// The document is created/updated
    	_, err = doc.Set(ctx, session)
    	if err != nil {
    		log.Printf("doc.Set error: %v", err)
    		http.Error(w, "Error creating session", http.StatusInternalServerError)
    		return
    	}
    
    	if err := a.tmpl.Execute(w, session); err != nil {
    		log.Printf("Execute: %v", err)
    	}
    }
    

    En el siguiente diagrama, se ilustra cómo Firestore maneja las sesiones para la aplicación Cloud Run.

    Diagrama de arquitectura: usuario, Cloud Run, Firestore.

Borra sesiones

Puedes borrar los datos de la sesión en la consola deGoogle Cloud o implementar una estrategia de eliminación automática. Cuando usas soluciones de almacenamiento para sesiones, como Memcache o Redis, las sesiones vencidas se borran de forma automática.

Ejecución local

  1. En la ventana de la terminal, compila el objeto binario sessions:

    go build
    
  2. Inicia el servidor HTTP:

    ./sessions
    
  3. Visualiza la app en el navegador web:

    Cloud Shell

    En la barra de herramientas de Cloud Shell, haz clic en Vista previa en la Web Vista previa en la Web y selecciona Vista previa en el puerto 8080.

    Máquina local

    En tu navegador, ve a http://localhost:8080.

    Ves uno de los cinco saludos: "Hello World", "Hallo Welt", "Hola mundo", "Salut le Monde" o "Ciao Mondo". El idioma cambia si abres la página en otro navegador o en modo Incógnito. Puedes ver y editar los datos de la sesión en la consola deGoogle Cloud .

    Sesiones de Firestore en la consola de Google Cloud

  4. Para detener el servidor HTTP, presiona Control+C en la ventana de la terminal.

Implementa y ejecuta en Cloud Run

Puede usar Cloud Run para crear y, luego, implementar una aplicación que se ejecute de manera confiable bajo una gran carga y con grandes cantidades de datos.

  1. Implementa la app en Cloud Run:
        gcloud run deploy firestore-tutorial-go 
    --source . --allow-unauthenticated --port=8080
    --set-env-vars=GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
  2. Visita la URL que muestra este comando para ver cómo persisten los datos de sesión entre las cargas de la página.

El saludo ahora se entrega en un servidor web que se ejecuta en una instancia de Cloud Run.

Depura la app

Si no puedes conectarte a la aplicación Cloud Run, verifica lo siguiente:

  1. Comprueba que los comandos de implementación de gcloud se completaron correctamente y no generaron ningún error. Si hubo errores (por ejemplo, message=Build failed), corrígelos y, luego, intenta implementar la aplicación Cloud Run nuevamente.
  2. En la Google Cloud consola, ve a la página Explorador de registros.

    Ir a la página Explorador de registros

    1. En la lista desplegable Recursos seleccionados recientemente, haz clic en Aplicación de Cloud Run, y, luego, haz clic en All module_id. Verás una lista de las solicitudes de cuando visitaste tu aplicación. Si no ves una lista de solicitudes, confirma que seleccionaste Todos module_id en la lista desplegable. Si ves mensajes de error impresos en la consola de Google Cloud , comprueba que el código de tu app coincida con el código de la sección sobre cómo escribir la app web.

    2. Asegúrate de que la API de Firestore esté habilitada.

Limpia

Borra el proyecto

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Borra la instancia de Cloud Run

  1. In the Google Cloud console, go to the Versions page for App Engine.

    Go to Versions

  2. Select the checkbox for the non-default app version that you want to delete.
  3. Para borrar la versión de la app, haz clic en Borrar.

¿Qué sigue?