Using Scala and Play 2.5.10 I implemented the following reusable action for composition and with the aim of disabling caching in the browser by changing the response headers:
import play.api.http.HeaderNames
import play.api.mvc._
import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
case class NoCache[A](action: Action[A]) extends Action[A] with HeaderNames {
def apply(request: Request[A]): Future[Result] = {
action(request).andThen {
case Success(result) => result.withHeaders(
(CACHE_CONTROL -> "no-cache, no-store, must-revalidate"),
(PRAGMA -> "no-cache"),
(EXPIRES -> "0")
)
case Failure(result) => result
}
}
lazy val parser = action.parser
}
I then reuse it in my controller action implementations like this:
def link = NoCache {
deadbolt.SubjectPresent()() { implicit request =>
Future {
Ok(views.html.account.link(userService, auth))
}
}
}
I breakpoint into the NoCache
implementation and it gets executed correctly, however, using the Web Developer Firefox plugin to monitor the network traffic I see that the response headers do not contain the "no cache" modifications ... what'm I doing wrong?
Problem with your code
The problem is with andThen
. andThen
discards the return value. So the transformed result
with new headers is getting discarded.
Remove andThen
and make it a map
.
andThen
is used for running side effecting computation just after the completion of the future on which it is invoked.
The computation of andThen
returns a Future with the same result that of original future and discards the return type of the computation inside andThen
.
Here is the implementation of andThen
from standard lib.
def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = {
val p = Promise[T]()
onComplete {
case r => try pf.applyOrElse[Try[T], Any](r, Predef.conforms[Try[T]]) finally p complete r
}
p.future
}
Correcting your code
case class NoCache[A](action: Action[A]) extends Action[A] with HeaderNames {
def apply(request: Request[A]): Future[Result] = {
action(request).map { result =>
result.withHeaders(
(CACHE_CONTROL -> "no-cache, no-store, must-revalidate"),
(PRAGMA -> "no-cache"),
(EXPIRES -> "0")
)
}
}
lazy val parser = action.parser
}
Other way to do that same
You can use Play filters Filter
to change the headers and also can be used for doing some pre processing and post processing work.
You can target only specific routes by checking the uri of the request.
import akka.stream.Materializer
import com.google.inject.{Inject, Singleton}
import play.api.http.DefaultHttpFilters
import play.api.mvc.{Filter, RequestHeader, Result}
import play.mvc.Http.HeaderNames
import scala.concurrent.Future
@Singleton
class Filters @Inject() (fooFilter: FooFilter) extends DefaultHttpFilters(fooFilter) {}
@Singleton
class FooFilter @Inject() (implicit override val mat: Materializer) extends Filter {
override def apply(f: (RequestHeader) => Future[Result])(rh: RequestHeader): Future[Result] = {
f(rh).map { result =>
if (rh.uri.startsWith("/foo"))
result.withHeaders(HeaderNames.CACHE_CONTROL -> "no-cache")
else result
}
}
}
In the above example for the /foo
route cache_control will be set and for the other routes same headers will be propagated.
Note create Filters inside the root folder of the play app if not you have add the filters to the application.conf
Never run heavy running computation inside the Filters, make Filters as light as possible.