Processar sessões com o Firestore

Este tutorial mostra como processar sessões no Cloud Run.

Muitas apps precisam de processamento de sessões para autenticação e preferências do utilizador. O pacote Gorilla Web Toolkit sessions é fornecido com uma implementação baseada no sistema de ficheiros para executar esta função. No entanto, esta implementação não é adequada para uma app que possa ser publicada a partir de várias instâncias, porque a sessão registada numa instância pode diferir de outras instâncias. O pacote gorilla/sessions também inclui uma implementação baseada em cookies. No entanto, esta implementação requer a encriptação de cookies e o armazenamento de toda a sessão no cliente, em vez de apenas um ID de sessão, o que pode ser demasiado grande para algumas apps.

Objetivos

  • Escreva a app.
  • Execute a app localmente.
  • Implemente a app no Cloud Run.

Custos

Neste documento, usa os seguintes componentes faturáveis do Cloud de Confiance by S3NS:

Quando terminar as tarefas descritas neste documento, pode evitar a faturação contínua eliminando os recursos que criou. Para mais informações, consulte o artigo Limpe.

Antes de começar

  1. In the Cloud de Confiance console, on the project selector page, select or create a Cloud de Confiance 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

  2. Verify that billing is enabled for your Cloud de Confiance project.

  3. 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

  4. Na Cloud de Confiance consola, abra a app no Cloud Shell.

    Aceda ao Cloud Shell

    O Cloud Shell dá acesso à linha de comandos aos recursos da nuvem diretamente a partir do navegador. Abra o Cloud Shell no navegador e clique em Continuar para transferir o código de exemplo e mudar para o diretório da app.

  5. No Cloud Shell, configure a CLI gcloud para usar o seu novo Cloud de Confiance projeto:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID
    

Configurar o projeto

  1. Na janela do terminal, clone o repositório da app de exemplo para a sua máquina local:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
  2. Altere para o diretório que contém o código de exemplo:

    cd golang-samples/getting-started/sessions

Compreender a app Web

Esta app apresenta saudações em diferentes idiomas para cada utilizador. Os utilizadores recorrentes são sempre recebidos no mesmo idioma.

Várias janelas de apps a apresentar uma saudação em diferentes idiomas.

Antes de a app poder armazenar preferências para um utilizador, precisa de uma forma de armazenar informações sobre o utilizador atual numa sessão. Esta app de exemplo usa o Firestore para armazenar dados de sessões.

  1. A app começa por importar dependências, definir um tipo app para conter um sessions.Store e um modelo HTML, e definir a lista de saudações.

    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. Em seguida, a app define uma função main, que cria uma nova instância app, regista o controlador de índice e inicia o servidor HTTP. A função newApp cria a instância app definindo os valores projectID e collectionID e analisa o modelo 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. O controlador de índice obtém a sessão do utilizador, criando uma, se necessário. Às novas sessões é atribuído um idioma aleatório e uma contagem de visualizações de 0. Em seguida, a contagem de visualizações é aumentada em um, a sessão é guardada e o modelo HTML escreve a resposta.

    
    // 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)
    	}
    }
    

    O diagrama seguinte ilustra como o Firestore processa as sessões para a app do Cloud Run.

    Diagrama da arquitetura: utilizador, Cloud Run, Firestore.

Eliminar sessões

Pode eliminar dados de sessões na Cloud de Confiance consola ou implementar uma estratégia de eliminação automática. Se usar soluções de armazenamento para sessões, como o Memcache ou o Redis, as sessões expiradas são eliminadas automaticamente.

Execução local

  1. Na janela do terminal, crie o ficheiro binário sessions:

    go build
    
  2. Inicie o servidor HTTP:

    ./sessions
    
  3. Veja a app no navegador de Internet:

    Cloud Shell

    Na barra de ferramentas do Cloud Shell, clique em Pré-visualização Web Pré-visualização da Web e selecione Pré-visualizar na porta 8080.

    Máquina local

    No navegador, aceda a http://localhost:8080

    É apresentada uma de cinco saudações: "Hello World", "Hallo Welt", "Hola mundo", "Salut le Monde" ou "Ciao Mondo". O idioma muda se abrir a página num navegador diferente ou no modo de navegação anónima. Pode ver e editar os dados da sessão na Cloud de Confiance consola.

    Sessões do Firestore na consola Cloud de Confiance .

  4. Para parar o servidor HTTP, na janela do terminal, prima Control+C.

Implementar e executar no Cloud Run

Pode usar o Cloud Run para criar e implementar uma app que é executada de forma fiável sob carga elevada e com grandes quantidades de dados.

  1. Implemente a app no Cloud Run:
        gcloud run deploy firestore-tutorial-go 
    --source . --allow-unauthenticated --port=8080
    --set-env-vars=GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
  2. Visite o URL devolvido por este comando para ver como os dados da sessão persistem entre carregamentos de páginas.

A saudação é agora fornecida por um servidor Web em execução numa instância do Cloud Run.

Depurar a app

Se não conseguir estabelecer ligação à sua app do Cloud Run, verifique o seguinte:

  1. Verifique se os comandos de implementação foram concluídos com êxito e não apresentaram erros.gcloud Se ocorreram erros (por exemplo, message=Build failed), corrija-os e tente implementar a app Cloud Run novamente.
  2. Na Cloud de Confiance consola, aceda à página Explorador de registos.

    Aceda à página do explorador de registos

    1. Na lista pendente Recursos selecionados recentemente, clique em Aplicação do Cloud Run e, de seguida, clique em Todos os module_id. É apresentada uma lista de pedidos de quando visitou a sua app. Se não vir uma lista de pedidos, confirme que selecionou Todos os module_id na lista pendente. Se vir mensagens de erro impressas na Cloud de Confiance consola, verifique se o código da sua app corresponde ao código na secção sobre como escrever a app Web.

    2. Certifique-se de que a API Firestore está ativada.

Limpar

Elimine o projeto

  1. In the Cloud de Confiance 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.

Elimine a instância do Cloud Run

  1. In the Cloud de Confiance 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 eliminar a versão da app, clique em Eliminar.

O que se segue?