טיפול בסשנים באמצעות Firestore

במדריך הזה מוסבר איך לנהל סשנים ב-Cloud Run.

באפליקציות רבות נדרש ניהול סשנים לצורך אימות והעדפות משתמש. חבילת Gorilla Web Toolkitsessions כוללת הטמעה מבוססת-מערכת קבצים לביצוע הפונקציה הזו. עם זאת, ההטמעה הזו לא מתאימה לאפליקציה שאפשר להציג אותה מכמה מופעים, כי יכול להיות שהסשן שמתועד במופע אחד יהיה שונה ממופעים אחרים. חבילת gorilla/sessions כוללת גם הטמעה שמבוססת על קובצי Cookie. אבל ההטמעה הזו מחייבת הצפנה של קובצי ה-cookie ואחסון של הסשן כולו בצד הלקוח, ולא רק של מזהה הסשן. יכול להיות שהגודל של הסשן יהיה גדול מדי עבור חלק מהאפליקציות.

מטרות

  • כותבים את האפליקציה.
  • מריצים את האפליקציה באופן מקומי.
  • פורסים את האפליקציה ב-Cloud Run.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Cloud de Confiance by S3NS, והשימוש בהם כרוך בתשלום:

כשמסיימים את המשימות שמתוארות במסמך הזה אפשר למחוק את המשאבים שיצרתם כדי להימנע מחיובים נוספים. מידע נוסף זמין בקטע הסרת המשאבים.

לפני שמתחילים

  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 role (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. במסוף Cloud de Confiance , פותחים את האפליקציה ב-Cloud Shell.

    כניסה ל-Cloud Shell

    ‫Cloud Shell מספק גישה למשאבי הענן דרך שורת הפקודה ישירות מהדפדפן. פותחים את Cloud Shell בדפדפן ולוחצים על המשך כדי להוריד את קוד הדוגמה ולעבור לספריית האפליקציה.

  5. ב-Cloud Shell, מגדירים את ה-CLI של gcloud לשימוש בפרויקט החדש Cloud de Confiance :
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID
    

הגדרת הפרויקט

  1. בחלון המסוף, משכפלים את מאגר האפליקציה לדוגמה למכונה המקומית:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
  2. עוברים לספרייה שמכילה את הקוד לדוגמה:

    cd golang-samples/getting-started/sessions

הסבר על אפליקציית האינטרנט

האפליקציה הזו מציגה ברכות בשפות שונות לכל משתמש. המשתמשים החוזרים תמיד יקבלו את פנייה באותה שפה.

כמה חלונות של אפליקציות שמוצגת בהם ברכה בשפות שונות.

כדי שהאפליקציה תוכל לאחסן את ההעדפות של המשתמש, צריך לאחסן מידע על המשתמש הנוכחי בסשן. אפליקציית הדוגמה הזו משתמשת ב-Firestore לאחסון נתוני סשנים.

  1. האפליקציה מתחילה בייבוא תלויות, בהגדרת סוג app להחזקת sessions.Store ותבנית HTML, ובהגדרת רשימת הברכות.

    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. לאחר מכן, האפליקציה מגדירה פונקציה main שיוצרת מופע app חדש, רושמת את handler האינדקס ומתחילה את שרת ה-HTTP. הפונקציה newApp יוצרת את המופע app על ידי הגדרת הערכים projectID ו-collectionID ומנתחת את תבנית ה-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. ה-handler של האינדקס מקבל את הסשן של המשתמש, ויוצר סשן אם צריך. לסשנים חדשים מוקצית שפה אקראית ומספר צפיות של 0. אחר כך, מספר הצפיות גדל באחת, הסשן נשמר ותבנית ה-HTML כותבת את התגובה.

    
    // 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,
    			HttpOnly: true,                 // Prevents client-side scripts from accessing the cookie
    			SameSite: http.SameSiteLaxMode, // Protects the session cookie from Cross-Site Request Forgery (CSRF) attacks
    		}
    		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)
    	}
    }
    

    בתרשים הבא מוצג אופן הטיפול של Firestore בסשנים באפליקציית Cloud Run.

    תרשים של הארכיטקטורה: משתמש, Cloud Run, ‏ Firestore.

מחיקת סשנים

אתם יכולים למחוק נתוני סשן במסוףCloud de Confiance או להטמיע אסטרטגיה למחיקה אוטומטית. אם אתם משתמשים בפתרונות אחסון לסשנים כמו Memcache או Redis, סשנים שפג תוקפם נמחקים אוטומטית.

הרצה מקומית

  1. בחלון המסוף, מפתחים את הקובץ הבינארי sessions:

    go build
    
  2. מפעילים את שרת ה-HTTP:

    ./sessions
    
  3. צפייה באפליקציה בדפדפן אינטרנט:

    Cloud Shell

    בסרגל הכלים של Cloud Shell, לוחצים על Web preview (תצוגה מקדימה של אתר) תצוגה מקדימה של אתר ובוחרים באפשרות Preview on port 8080 (תצוגה מקדימה ביציאה 8080).

    מכונה מקומית

    בדפדפן, עוברים אל http://localhost:8080

    מוצגת אחת מ-5 ברכות: Hello World,‏ Hallo Welt,‏ Hola mundo,‏ Salut le Monde או Ciao Mondo. השפה משתנה אם פותחים את הדף בדפדפן אחר או במצב פרטי. אפשר לראות ולערוך את נתוני הסשן בCloud de Confiance מסוף.

    פעילויות ב-Firestore במסוף Cloud de Confiance .

  4. כדי לעצור את שרת ה-HTTP, בחלון הטרמינל לוחצים על Control+C.

פריסה והפעלה ב-Cloud Run

אתם יכולים להשתמש ב-Cloud Run כדי ליצור ולפרוס אפליקציה שפועלת בצורה מהימנה בעומס כבד ועם כמויות גדולות של נתונים.

  1. פורסים את האפליקציה ב-Cloud Run:
        gcloud run deploy firestore-tutorial-go 
    --source . --allow-unauthenticated --port=8080
    --set-env-vars=GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
  2. כדי לראות איך נתוני הסשן נשמרים בין טעינות דפים, עוברים לכתובת ה-URL שמוחזרת על ידי הפקודה הזו.

הברכה מועברת עכשיו על ידי שרת אינטרנט שפועל במופע של Cloud Run.

ניפוי באגים באפליקציה

אם אתם לא מצליחים להתחבר לאפליקציית Cloud Run, כדאי לבדוק את הדברים הבאים:

  1. בודקים שהפקודות gcloud deploy הושלמו בהצלחה ולא הוחזרו שגיאות. אם היו שגיאות (לדוגמה, message=Build failed), תקנו אותן ונסו לפרוס את אפליקציית Cloud Run שוב.
  2. נכנסים לדף Logs Explorer במסוף Cloud de Confiance .

    כניסה לדף Logs Explorer

    1. בתפריט הנפתח Recently selected resources, לוחצים על Cloud Run Application ואז על All module_id. מוצגת רשימה של בקשות מהביקור שלכם באפליקציה. אם לא מוצגת רשימה של בקשות, צריך לוודא שבחרתם באפשרות All module_id מהרשימה הנפתחת. אם הודעות השגיאה מודפסות ב Cloud de Confiance מסוף, צריך לוודא שהקוד של האפליקציה זהה לקוד שבקטע על כתיבת אפליקציית האינטרנט.

    2. מוודאים ש-Firestore API מופעל.

הסרת המשאבים

מחיקת הפרויקט

  1. במסוף Cloud de Confiance , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

מחיקת מופע Cloud Run

  1. במסוף Cloud de Confiance , נכנסים לדף Versions של App Engine.

    כניסה לדף Versions

  2. מסמנים את התיבה שלצד גרסת האפליקציה שלא מוגדרת כברירת מחדל ושרוצים למחוק.
  3. כדי למחוק את גרסת האפליקציה, לוחצים על Delete.

המאמרים הבאים