|
Play Framework/Scala example source code file (Cached.scala)
The Cached.scala Play Framework example source code/* * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com> */ package play.api.cache import play.api._ import play.api.mvc._ import play.api.libs.iteratee.{ Iteratee, Done } import play.api.http.HeaderNames.{ IF_NONE_MATCH, ETAG, EXPIRES } import play.api.mvc.Results.NotModified import play.core.Execution.Implicits.internalContext import scala.concurrent.duration._ /** * Cache an action. * * Uses both server and client caches: * * - Adds an `Expires` header to the response, so clients can cache response content ; * - Adds an `Etag` header to the response, so clients can cache response content and ask the server for freshness ; * - Cache the result on the server, so the underlying action is not computed at each call. * * @param key Compute a key from the request header * @param caching A callback to get the number of seconds to cache results for */ case class Cached(key: RequestHeader => String, caching: PartialFunction[ResponseHeader, Duration]) { import Cached._ def apply(action: EssentialAction)(implicit app: Application) = build(action) /** * Compose the cache with an action */ 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)) } } /** * Eternity is one year long. Duration zero means eternity. */ private val cachingWithEternity = caching.andThen { duration => if (duration.zero) { Duration(60 * 60 * 24 * 365, SECONDS) } else { duration } } 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) } /** * Whether this cache should cache the specified response if the status code match * This method will cache the result forever */ def includeStatus(status: Int): Cached = includeStatus(status, Duration.Zero) /** * Whether this cache should cache the specified response if the status code match * This method will cache the result for duration seconds * * @param status the status code to check * @param duration the number of seconds to cache the result for */ def includeStatus(status: Int, duration: Int): Cached = includeStatus(status, Duration(duration, SECONDS)) /** * Whether this cache should cache the specified response if the status code match * This method will cache the result for duration seconds * * @param status the status code to check * @param duration how long should we cache the result for */ def includeStatus(status: Int, duration: Duration): Cached = this.copy(caching = caching.orElse { case e if e.status == status => { duration } }) /** * The returned cache will store all responses whatever they may contain * @param duration how long we should store responses */ def default(duration: Duration): Cached = compose(PartialFunction((_: ResponseHeader) => duration)) /** * The returned cache will store all responses whatever they may contain * @param duration the number of seconds we should store responses */ def default(duration: Int): Cached = default(Duration(duration, SECONDS)) /** * Compose the cache with new caching function * @param alternative a closure getting the reponseheader and returning the duration * we should cache for */ def compose(alternative: PartialFunction[ResponseHeader, Duration]): Cached = this.copy(caching = caching.orElse(alternative)) } object Cached { /** * Convenient implicit class for adding a zero method to Duration */ private implicit class DurationEmpty(d: Duration) { /** * This tests if the duration is currently zero * @returns boolean checking whether the duration is zero or not */ def zero(): Boolean = d.neg().equals(d) } /** * Cache an action. * * @param key Compute a key from the request header */ def apply(key: RequestHeader => String): Cached = { apply(key, duration = 0) } /** * Cache an action. * * @param key Cache key */ def apply(key: String): Cached = { apply(_ => key, duration = 0) } /** * Cache an action. * * @param key Cache key * @param duration Cache duration (in seconds) */ def apply(key: RequestHeader => String, duration: Int): Cached = { new Cached(key, { case (_: ResponseHeader) => Duration(duration, SECONDS) }) } /** * A cached instance caching nothing * Useful for composition */ def empty(key: RequestHeader => String): Cached = new Cached(key, PartialFunction.empty) /** * Caches everything, forever */ def everything(key: RequestHeader => String): Cached = empty(key).default(0) /** * Caches everything for the specified seconds */ def everything(key: RequestHeader => String, duration: Int): Cached = empty(key).default(duration) /** * Caches the specified status, for the specified number of seconds */ def status(key: RequestHeader => String, status: Int, duration: Int): Cached = empty(key).includeStatus(status, Duration(duration, SECONDS)) /** * Caches the specified status forever */ def status(key: RequestHeader => String, status: Int): Cached = empty(key).includeStatus(status) } Other Play Framework source code examplesHere is a short list of links related to this Play Framework Cached.scala source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.