The Cache API

Caching in a web application is the process of storing dynamically generated items, whether these are data objects, pages, or parts of a page, in memory at the initial time they are requested. This can later be reused if subsequent requests for the same data are made, thereby reducing response time and enhancing user experience. One can cache or store these items on the web server or other software in the request stream, such as the proxy server or browser.

Play has a minimal cache API, which uses EHCache. As stated on its website (http://ehcache.org/):

Ehcache is an open source, standards-based cache for boosting performance, offloading your database, and simplifying scalability. It's the most widely-used Java-based cache because it's robust, proven, and full-featured. Ehcache scales from in-process, with one or more nodes, all the way to mixed in-process/out-of-process configurations with terabyte-sized caches.

It provides caching for presentation layers as well as application-specific objects. It is easy to use, maintain, and extend.

Note

To use the default cache API within a Play application, we should declare it as a dependency as follows:

libraryDependencies ++= Seq(cache)

Using the default cache API is similar to using a mutable Map[String, Any]:

Cache.set("userSession", session)

val maybeSession: Option[UserSession] = Cache.getAs[UserSession]("userSession")

Cache.remove("userSession")

This API is made available through EHCachePlugin. The plugin is responsible for creating an instance of EHCache CacheManager with an available configuration on starting the application, and shutting it down when the application is stopped. We will discuss Play plugins in detail in Chapter 13, Writing Play Plugins. Basically, EHCachePlugin handles all the boilerplate required to use EHCache in an application and EhCacheImpl provides the methods to do so, such as get, set, and remove. It is defined as follows:

class EhCacheImpl(private val cache: Ehcache) extends CacheAPI {

  def set(key: String, value: Any, expiration: Int) {
    val element = new Element(key, value)
    if (expiration == 0) element.setEternal(true)
    element.setTimeToLive(expiration)
    cache.put(element)
  }

  def get(key: String): Option[Any] = {
    Option(cache.get(key)).map(_.getObjectValue)
  }

  def remove(key: String) {
    cache.remove(key)
  }
}

Note

By default, the plugin looks for ehcache.xml in the conf directory and, if the file does not exist, the default configuration provided by the ehcache-default.xml framework is loaded.

It is also possible to specify the location of the ehcache configuration when starting the application using the ehcache.configResource argument.

The Cache API also simplifies handling a cache for results from requests on both the client and server side of the application. Adding EXPIRES and etag headers can be used to manipulate the client-side cache, while on the server side the results are cached so that its corresponding action is not computed for each call.

For example, we can cache the result of the request used to fetch details of inactive users:

def getInactiveUsers = Cached("inactiveUsers") {
  Action {
    val users = User.getAllInactive
    Ok(Json.toJson(users))
  }
}

However, what if we want this to get updated every hour? We just need to specify the duration explicitly:

def getInactiveUsers = Cached("inactiveUsers").default(3600) {
  Action {
    val users = User.getAllInactive
    Ok(Json.toJson(users))
  }
}

All of this is handled by the Cached case class and its companion object. The case class is defined as follows:

case class Cached(key: RequestHeader => String, caching: PartialFunction[ResponseHeader, Duration]) { … }

The companion object provides commonly required methods to generate cached instances, such as cache action based on its status, and so on.

The apply method in cached calls the build method, which is defined as follows:

def build(action: EssentialAction)(implicit app: Application) = EssentialAction { request =>
    val resultKey = key(request)
    val etagKey = s"$resultKey-etag"

    // Has the client a version of the resource as fresh as the last one we served?
    val notModified = for {
      requestEtag <- request.headers.get(IF_NONE_MATCH)
      etag <- Cache.getAs[String](etagKey)
      if requestEtag == "*" || etag == requestEtag
    } yield Done[Array[Byte], Result](NotModified)

    notModified.orElse {
      // Otherwise try to serve the resource from the cache, if it has not yet expired
      Cache.getAs[Result](resultKey).map(Done[Array[Byte], Result](_))
    }.getOrElse {
      // The resource was not in the cache, we have to run the underlying action
      val iterateeResult = action(request)

      // Add cache information to the response, so clients can cache its content
      iterateeResult.map(handleResult(_, etagKey, resultKey, app))
    }
  }

It simply checks if the result was modified or not. If it hasn't been, it tries to get the result from the Cache. If the result does not exist in the cache, it fetches it from the action and adds it to the Cache using the handleResult method. The handleResult method is defined as follows:

private def handleResult(result: Result, etagKey: String, resultKey: String, app: Application): Result = {
  cachingWithEternity.andThen { duration =>
    // Format expiration date according to http standard
    val expirationDate = http.dateFormat.print(System.currentTimeMillis() + duration.toMillis)
      // Generate a fresh ETAG for it
      val etag = expirationDate // Use the expiration date as ETAG

      val resultWithHeaders = result.withHeaders(ETAG -> etag, EXPIRES -> expirationDate)

      // Cache the new ETAG of the resource
      Cache.set(etagKey, etag, duration)(app)
      // Cache the new Result of the resource
      Cache.set(resultKey, resultWithHeaders, duration)(app)

      resultWithHeaders
    }.applyOrElse(result.header, (_: ResponseHeader) => result)
  }

If a duration is specified, it returns that else it returns the default duration of one year.

The handleResult method simply takes the result, adds etag, expires headers, and then adds the result with the given key to Cache.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.93.0