Handling sessions with Firestore

This tutorial shows how to handle sessions on Cloud Run.

Many apps need session handling for authentication and user preferences. The Jetty framework comes with a memory-based implementation to perform this function. However, this implementation is unsuitable for an app that can be served from multiple instances, because the session that is recorded in one instance might differ from other instances. This tutorial shows how to handle sessions on Cloud Run.

Objectives

  • Write the app.
  • Run the app locally.
  • Deploy the app on Cloud Run.

Costs

This tutorial uses the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

When you finish this tutorial, you can avoid continued billing by deleting the resources you created. For more information, see Cleaning up.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Cloud Console, on the project selector page, select or create a Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Google Cloud project. Learn how to confirm billing is enabled for your project.

  4. Enable the Firestore API.

    Enable the API

  5. In the Google Cloud Console, open the app in Cloud Shell.

    Go to Cloud Shell

    Cloud Shell provides command-line access to your cloud resources directly from the browser. Open Cloud Shell in your browser and click Proceed to download the sample code and change into the app directory.

  6. In Cloud Shell, configure the gcloud tool to use your new Google Cloud project:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID
    
  7. Update Maven to use Java 11 by default:
    sudo update-alternatives --config java
    
    When prompted, enter the number to select Java 11. Make a note of the path listed for that version.
  8. Export the path that you copied in the previous step as an environment variable:
    export JAVA_HOME=java-11-path
    

The web app

This app displays greetings in different languages for every user. Returning users are always greeted in the same language.

Multiple app windows displaying a greeting in different languages.

Before your app can store preferences for a user, you need a way to store information about the current user in a session. This sample app uses a WebFilter to retrieve and update session data in Firestore.

@Override
public void init(FilterConfig config) throws ServletException {
  // Initialize local copy of datastore session variables.
  firestore = FirestoreOptions.getDefaultInstance().getService();
  sessions = firestore.collection("sessions");

  try {
    // Delete all sessions unmodified for over two days.
    Calendar cal = Calendar.getInstance();
    cal.setTime(new Date());
    cal.add(Calendar.HOUR, -48);
    Date twoDaysAgo = Calendar.getInstance().getTime();
    QuerySnapshot sessionDocs =
        sessions.whereLessThan("lastModified", dtf.format(twoDaysAgo)).get().get();
    for (QueryDocumentSnapshot snapshot : sessionDocs.getDocuments()) {
      snapshot.getReference().delete();
    }
  } catch (InterruptedException | ExecutionException e) {
    throw new ServletException("Exception initializing FirestoreSessionFilter.", e);
  }
}

The following diagram illustrates how Firestore handles sessions for the Cloud Run app.

Diagram of architecture: user, Cloud Run, Firestore.

The HttpServletRequest uses a cookie to store a unique ID for the local session, which corresponds to a document in Firestore with the session details.

Deleting sessions

Firestore doesn't delete old or expired sessions. You can delete session data in the Google Cloud Console or implement an automated deletion strategy. If you use storage solutions for sessions such as Memcache or Redis, expired sessions are automatically deleted.

Running locally

  1. Start the HTTP server:

    mvn jetty:run
    
  2. View the app in your web browser:

    Cloud Shell

    In the Cloud Shell toolbar, click Web preview Web preview and select Preview on port 8080.

    Local machine

    In your browser, go to http://localhost:8080

    You see one of five greetings: "Hello World", "Hallo Welt", "Hola mundo", "Salut le Monde", or "Ciao Mondo." The language changes if you open the page in a different browser or in incognito mode. You can see and edit the session data in the Google Cloud Console.

    Firestore sessions in Cloud Console.

  3. To stop the HTTP server, in your terminal window, press Control+C.

Deploying and running on Cloud Run

You can use the Cloud Run to build and deploy an app that runs reliably under heavy load and with large amounts of data.

  1. In your terminal window, build and deploy an image of your code to Google Container Registry (GCR) with the Jib Maven plugin.

    mvn clean package jib:build

  2. Deploy the app to Cloud Run:

       gcloud beta run deploy session-handling --image gcr.io/MY_PROJECT/session-handling 
    --platform managed --region us-central1 --memory 512M

    Replace MY_PROJECT with the ID of the Cloud project you created. Visit the URL returned by this command to see how session data persists between page loads.

Debugging the app

If you cannot connect to your Cloud Run app, check the following:

  1. Check that the gcloud deploy commands successfully completed and didn't output any errors. If there were errors (for example, message=Build failed), fix them, and try deploying the Cloud Run app again.
  2. In the Cloud Console, go to the Logs Viewer page.

    Go to Logs Viewer page

    1. In the Recently selected resources drop-down list, click Cloud Run Application, and then click All module_id. You see a list of requests from when you visited your app. If you don't see a list of requests, confirm you selected All module_id from the drop-down list. If you see error messages printed to the Cloud Console, check that your app's code matches the code in the section about writing the web app.

    2. Make sure that the Firestore API is enabled.

Cleaning up

Delete the project

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  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.

Delete the Cloud Run instance

Delete the service from Cloud Run.

  • In the Cloud Console, go to the Services page for Cloud Run.

    Go to the Services page

  • Select the service you wish to delete.
  • Click Delete to delete the service.

What's next