spray.routing.directives.CachingDirectives.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spray-routing Show documentation
Show all versions of spray-routing Show documentation
A suite of lightweight Scala libraries for building and consuming RESTful web services on top of Akka
/*
* Copyright © 2011-2013 the spray project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package spray.routing
package directives
import akka.actor.{ Status, ActorRefFactory }
import scala.concurrent.duration.Duration
import scala.concurrent.{ Promise, ExecutionContext }
import scala.util.{ Success, Failure }
import spray.caching._
import spray.http._
import CacheDirectives._
import HttpHeaders._
import HttpMethods._
// not mixed into spray.routing.Directives due to its dependency on com.googlecode.concurrentlinkedhashmap
trait CachingDirectives {
import BasicDirectives._
import RouteDirectives._
type RouteResponse = Either[Seq[Rejection], HttpResponse]
/**
* Wraps its inner Route with caching support using the given [[spray.caching.Cache]] implementation and
* the in-scope keyer function.
*/
def cache(csm: CacheSpecMagnet): Directive0 = cachingProhibited | alwaysCache(csm)
/**
* Passes only requests to the inner route that explicitly forbid caching with a `Cache-Control` header with either
* a `no-cache` or `max-age=0` setting.
*/
def cachingProhibited: Directive0 =
extract(_.request.headers.exists {
case x: `Cache-Control` ⇒ x.directives.exists {
case `no-cache` ⇒ true
case `max-age`(0) ⇒ true
case _ ⇒ false
}
case _ ⇒ false
}).flatMap(if (_) pass else reject)
/**
* Wraps its inner Route with caching support using the given [[spray.caching.Cache]] implementation and
* in-scope keyer function. Note that routes producing streaming responses cannot be wrapped with this directive.
* Route responses other than HttpResponse or Rejections trigger a "500 Internal Server Error" response.
*/
def alwaysCache(csm: CacheSpecMagnet): Directive0 = {
import csm._
mapInnerRoute { route ⇒
ctx ⇒
liftedKeyer(ctx) match {
case Some(key) ⇒
responseCache(key) { (promise: Promise[RouteResponse]) ⇒
route {
ctx.withRouteResponseHandling {
case response: HttpResponse ⇒ promise.success(Right(response))
case Rejected(rejections) ⇒ promise.success(Left(rejections))
case Status.Failure(e) ⇒ promise.failure(e)
case x ⇒ promise.failure(new RequestProcessingException(StatusCodes.InternalServerError,
s"Route responses other than HttpResponse or Rejections cannot be cached (received: $x)"))
}
}
} onComplete {
case Success(Right(response)) ⇒ ctx.complete(response)
case Success(Left(rejections)) ⇒ ctx.reject(rejections: _*)
case Failure(error) ⇒ ctx.failWith(error)
}
case None ⇒ route(ctx)
}
}
}
//# route-Cache
def routeCache(maxCapacity: Int = 500, initialCapacity: Int = 16, timeToLive: Duration = Duration.Inf,
timeToIdle: Duration = Duration.Inf): Cache[RouteResponse] =
LruCache(maxCapacity, initialCapacity, timeToLive, timeToIdle)
//#
}
object CachingDirectives extends CachingDirectives
trait CacheSpecMagnet {
def responseCache: Cache[CachingDirectives.RouteResponse]
def liftedKeyer: RequestContext ⇒ Option[Any]
implicit def executionContext: ExecutionContext
}
object CacheSpecMagnet {
implicit def apply(cache: Cache[CachingDirectives.RouteResponse])(implicit keyer: CacheKeyer, factory: ActorRefFactory) = // # CacheSpecMagnet
new CacheSpecMagnet {
def responseCache = cache
def liftedKeyer = keyer.lift
implicit def executionContext = factory.dispatcher
}
}
trait CacheKeyer extends (PartialFunction[RequestContext, Any])
object CacheKeyer {
implicit val Default: CacheKeyer = CacheKeyer {
case RequestContext(HttpRequest(GET, uri, _, _, _), _, _) ⇒ uri
}
def apply(f: PartialFunction[RequestContext, Any]) = new CacheKeyer {
def isDefinedAt(ctx: RequestContext) = f.isDefinedAt(ctx)
def apply(ctx: RequestContext) = f(ctx)
}
}