57 lines
1.8 KiB
Scala
57 lines
1.8 KiB
Scala
package controllers.caching
|
|
|
|
import play.api.mvc.Results.NotModified
|
|
import play.api.mvc.{Request, Result}
|
|
|
|
import java.io.{ByteArrayOutputStream, DataOutputStream, OutputStream}
|
|
import java.security.{DigestOutputStream, MessageDigest}
|
|
import java.util.Base64
|
|
import scala.concurrent.{ExecutionContext, Future}
|
|
import scala.util.Using
|
|
|
|
trait Etagable[A]:
|
|
extension (a: A) def etag: String
|
|
|
|
|
|
trait DigestEtagable[A] extends Etagable[A]:
|
|
private val base64 = Base64.getUrlEncoder.withoutPadding
|
|
|
|
protected def writeDigestData(a: A, s: DataOutputStream): Unit
|
|
|
|
extension (a: A) {
|
|
override def etag: String = {
|
|
val md = MessageDigest.getInstance("SHA-1")
|
|
Using(DataOutputStream(DigestOutputStream(OutputStream.nullOutputStream(), md))) { dos =>
|
|
writeDigestData(a, dos)
|
|
dos.flush()
|
|
}
|
|
|
|
base64.encodeToString(md.digest())
|
|
}
|
|
}
|
|
|
|
|
|
trait CheckFresh {
|
|
protected def checkFreshAsync[A, E: Etagable](entity: E)(onStale: => Future[Result])(using request: Request[A], ec: ExecutionContext): Future[Result] = {
|
|
compareEtag(request, entity).fold(
|
|
entityEtag => onStale.map(_.withHeaders("etag" -> entityEtag)),
|
|
requestEtag => Future.successful(notModified(requestEtag))
|
|
)
|
|
}
|
|
|
|
protected def checkFresh[A, E: Etagable](entity: E)(onStale: => Result)(using request: Request[A]): Result = {
|
|
compareEtag(request, entity).fold(
|
|
entityEtag => onStale.withHeaders("etag" -> entityEtag),
|
|
requestEtag => notModified(requestEtag)
|
|
)
|
|
}
|
|
|
|
private def compareEtag[A, E: Etagable](request: Request[A], entity: E) = {
|
|
val entityEtag = entity.etag
|
|
request.headers.get("If-None-Match").filter(e => e == entityEtag) match {
|
|
case Some(etag) => Right(etag)
|
|
case None => Left(entityEtag)
|
|
}
|
|
}
|
|
private def notModified(etag: String) = NotModified.withHeaders("etag" -> etag)
|
|
} |