Enterprise Java

Play! Framework Session Example

Today, on most web applications, there is data we need to be sent across multiple http requests while the user has a browser tab open. A lot of different approaches were made, such as cookies and headers forwarding. To solve this problem, play applications now have a powerful tool: Sessions and Flash scope.

Imagine you need to track what a user does in your site. Some of the actions a client can do on your page trigger a lot of http requests, such as ajax requests, images requests, redirects, etc. It becomes pretty difficult to save data across all these requests without using server’s resources, until you find Play’s session support.

1. The Theory

Session and Flash data are not stored on the server, they are cookies, that means that data size is very limited (4kB) and that you can only store String values.

1.1. Sessions

Session cookies are signed with a secret key so the client can’t modify its data, or it will be invalidated, and its values are shared across every http request made from the same browser. Another thing to have in mind, is that sessions expire only when the user closes the web browser, there is no way to configure a functional time out. Whenever you need to do this (such as maximum inactivity duration), save a time stamp in the session and use it however you want. We’ll see an example of this case below.

1.2. Flash Scope

Flash scope data, on the other hand, is kept for only one request, and its cookie is not signed so the user will be able to modify its cookie value. Any way, given that this data will be available during only one http request, this should not be a problem.

For these reasons, flash scope is meant to be used in simple non-Ajax applications. You could use it to send a status across redirects. You have an action that saves an object and then redirects to another page, and in that other page it would be nice to have the status of that save operation. Flash scope is meant to solve that kind of problems.

Now check out this example on how to get a play application running, and come back to see some session and flash scope examples.

2. The code

2.1. Session

Let’s write a view for our first time visit and another one for other visits in our views package:

firstVisit.scala.html

@()
@main("Welcome!") {

    <h1>I see this is the first time you open this page on this browser session.</h1>

}

afterFirstVisit.scala.html

@()
@main("Oh Hello!") {

    <h1>I see this is <strong>NOT</strong> the first time you open this page on this browser session.</h1>

}

These views define a title ("Welcome!" for firstVisit and "Oh Hello!" for afterFristVisit) and an h1, which is sent to main which is another template located in the same package.

Now let’s write, in our Application controller in controllers package, an action that renders firstVisit of it’s the first time the user access this action in his browser session, or afterFirstVisit if not.

Application.java

package controllers;

import play.*;
import play.mvc.*;

import views.html.*;

import java.util.Optional;

public class Application extends Controller {

    public Result index() {
        return Optional.ofNullable(session("not_the_first_time"))
            .map(s -> ok(afterFirstVisit.render())).orElseGet(() -> {
                session().put("not_the_first_time", "true");
                return ok(firstVisit.render());
            });
    }

}

Here we are retrieving an entry called "not_the_first_time" from session and creating an optional of it. As we just want to know if it is there, and its value is not important for us right now, we make a map to return afterFirstVisit if it’s present and then we call orElseGet to put a value there and return firstVisit if it’s absent, which means it is the first time the client opens our page.

Application.scala

package controllers

import play.api._
import play.api.mvc._

class Application extends Controller {

  def index = Action { implicit request =>
    request.session.get("not_the_first_time").map(_ => Ok(views.html.afterFirstVisit())) match {
      case Some(r) => r
      case None => Ok(views.html.firstVisit()) withSession("not_the_first_time" -> "true")
    }
  }
  
}

The scala way is pretty much the same. We receive the request, read "not_the_first_time" from the session in it, which returns an Option, we call map to return afterFirstVisit if that entry is present and then we match it to know if it’s present to return it, or if it’s absent to return firstVisit and put a value in the session.

Now, let’s run the application with activator run and visit http://localhost:9000/. We’ll see just a big h1 saying: “I see this is the first time you open this page on this browser session.”, but when we hit refresh, the page will now say: “I see this is NOT the first time you open this page on this browser session.”, and that’s the result we wanted to get. If you close your browser and open it again, you will see this result again.

What if we want to add a maximum inactivity time?

Application.java

package controllers;

import play.*;
import play.api.*;
import play.mvc.*;

import views.html.*;

import java.util.Date;
import java.util.Optional;

public class Application extends Controller {

    private static final long maxInactivityTime = 300000L; // 5 minutes
    private static final String LAST_SEEN = "last_seen";

    public Result index() {
        return Optional.ofNullable(session(LAST_SEEN))
            .map(s -> new Date().getTime() - Long.valueOf(s) > maxInactivityTime ? renderFirstVisit() : renderAfterFirstVisit())
                .orElseGet(this::renderFirstVisit);
    }

    private Result renderAfterFirstVisit() {
        updateLastSeen();
        return ok(afterFirstVisit.render());
    }

    private Result renderFirstVisit() {
        updateLastSeen();
        return ok(firstVisit.render());
    }

    private void updateLastSeen() {
        session().put(LAST_SEEN, String.valueOf(new Date().getTime()));
    }

}

Application.scala

package controllers

import java.util.Date

import play.api._
import play.api.mvc._
import play.twirl.api.Html

class Application extends Controller {

  private val maxInactivityTime = 300000L // 5 minutes
  private val LAST_SEEN = "last_seen"

  def index = Action { implicit request =>
    request.session.get(LAST_SEEN).map(s =>
      new Date().getTime - s.toLong > maxInactivityTime match {
        case true => renderFirstVisit
        case false => renderAfterFirstVisit
      }
    ).getOrElse(renderFirstVisit)
  }

  private def renderFirstVisit = render(views.html.firstVisit())

  private def renderAfterFirstVisit = render(views.html.afterFirstVisit())

  private def render(view: Html) = Ok(view) withSession(LAST_SEEN -> new Date().getTime.toString)

}

In this case, both scala an java codes look pretty much the same.

Here, we defined a constant called maxInactivityTime and a new entry key for our session called LAST_SEEN.

The algorithm is pretty simple, we still read our key from the request’s session and make an Optional of that value. Then we use map to check if the time spent from that time stamp is greater than our maxInactivityTime, which holds 5 minutes. If the session is expired we update our last seen entry in the session and render our first visit page, else we update that entry again but we render our after first visit page. As a fall back, if the request’s session doesn’t hold our last seen entry, we write it and render our first visit page anyway.

2.2. Flash Scope

To see how the Flash Scope works, we’ll add an endpoint to change some message in our page. For this purpose we’ll add a parameter to our views called message and append it to a paragraph in our site.

firstVisit.scala.html

@(message: String)
@main("Welcome!") {

    <h1>I see this is the first time you open this page on this browser session.</h1>
    <p>@message</p>
}

afterFirstVisit.scala.html

@(message: String)
@main("Oh Hello!") {

    <h1>I see this is <strong>NOT</strong> the first time you open this page on this browser session.</h1>
    <p>@message</p>

}

Once this is done, we add this functionality to our controller.

Application.java

package controllers;

import play.*;
import play.api.*;
import play.mvc.*;

import views.html.*;

import java.util.Date;
import java.util.Optional;

public class Application extends Controller {

    private static final long maxInactivityTime = 300000L; // 5 minutes
    private static final String LAST_SEEN = "last_seen";

    public Result index() {
        String message = Optional.ofNullable(flash("message")).orElse("Welcome!");
        return Optional.ofNullable(session(LAST_SEEN))
            .map(s -> new Date().getTime() - Long.valueOf(s) > maxInactivityTime ? renderFirstVisit(message) : renderAfterFirstVisit(message))
                .orElseGet(() -> renderFirstVisit(message));
    }

    public Result changeMessage(String message) {
        flash("message", message);
        return redirect("/");
    }

    private Result renderAfterFirstVisit(String message) {
        updateLastSeen();
        return ok(afterFirstVisit.render(message));
    }

    private Result renderFirstVisit(String message) {
        updateLastSeen();
        return ok(firstVisit.render(message));
    }

    private void updateLastSeen() {
        session().put(LAST_SEEN, String.valueOf(new Date().getTime()));
    }

}

Application.scala

package controllers

import java.util.Date

import play.api._
import play.api.mvc._
import play.twirl.api.Html

class Application extends Controller {

  private val maxInactivityTime = 300000L // 5 minutes
  private val LAST_SEEN = "last_seen"
  private val MESSAGE = "message"

  def index = Action { implicit request =>
    val message = request.flash.get(MESSAGE).getOrElse("Welcome!")
    request.session.get(LAST_SEEN).map(s =>
      new Date().getTime - s.toLong > maxInactivityTime match {
        case true => renderFirstVisit(message)
        case false => renderAfterFirstVisit(message)
      }
    ).getOrElse(renderFirstVisit(message))
  }

  private def renderFirstVisit(message: String) = render(views.html.firstVisit(message))

  private def renderAfterFirstVisit(message: String) = render(views.html.afterFirstVisit(message))

  private def render(view: Html) = Ok(view) withSession(LAST_SEEN -> new Date().getTime.toString)

  def changeMessage(message: String) = Action { implicit request =>
    Redirect("/") flashing(MESSAGE -> message)
  }

}

As you can see, there is a new action called changeMessage, which receives a message as a parameter and flashes it in a redirect to /. As we’ve seen before, flash scope data will only last through one http request, and will not be available after its execution.

Then, in our index action, we read our flash data, and if it’s present we save it in a value, with a default value "Welcome!", then we pass along that value to our views.

routes

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index
GET     /:message                   controllers.Application.changeMessage(message: String)

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

Then we route our new action in our routes file, with that message as a path variable.

If we run our application now with activator run and visit http://localhost:9000/my%20message, we will now see, below our already known h1 a new paragraph, which says “my message”.

3. Download the Code Project

This was an example on how to exploit Play! Framework’s session and flash scope support.

Download
You can download the full source code of this example here: play-scala-example, play-java-example

Sebastian Vinci

Sebastian is a full stack programmer, who has strong experience in Java and Scala enterprise web applications. He is currently studying Computers Science in UBA (University of Buenos Aires) and working a full time job at a .com company as a Semi-Senior developer, involving architectural design, implementation and monitoring. He also worked in automating processes (such as data base backups, building, deploying and monitoring applications).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Sarah
Sarah
4 years ago

Thank you!
I have a question regarding the last seen value. How did you set it as a cookie? How can we reach the value just by ‘private val LAST_SEEN = “last_seen”‘?

Back to top button