Skip to content

Instantly share code, notes, and snippets.

@Slakah
Created August 27, 2018 12:42
Show Gist options
  • Save Slakah/670e932dace0b669c55eae3d6b386a54 to your computer and use it in GitHub Desktop.
Save Slakah/670e932dace0b669c55eae3d6b386a54 to your computer and use it in GitHub Desktop.
http4s caching middleware
package com.gubbns
import cats.data.{Kleisli, OptionT}
import cats.effect._
import cats.implicits._
import com.github.blemale.scaffeine.{Cache, Scaffeine}
import org.http4s._
import org.http4s.headers._
import org.http4s.server.Middleware
/** Caching middleware, based on request + method */
class HttpCaching[F[_], G[_]] private ()(implicit F: Sync[F]) {
private val cache: Cache[(Uri, Method), Response[G]] = Scaffeine().build()
private def getCached(request: Request[G], http: Http[F, G]) = {
if (request.method.isSafe) {
getIfPresent(request)
.getOrElseF(httpAndCache(request, http))
} else {
http(request)
}
}
private def getIfPresent(request: Request[G]) = {
OptionT(F.delay(cache.getIfPresent(request.uri -> request.method)))
}
private def httpAndCache(request: Request[G], http: Http[F, G]) = {
for {
resp <- http(request)
shouldCache = isCacheable(resp)
_ <- if (shouldCache) {
F.delay(cache.put(request.uri -> request.method, resp))
} else {
F.suspend(F.unit)
}
} yield resp
}
private def isCacheable(resp: Response[G]): Boolean = {
import Status._
!resp.headers
.get(`Cache-Control`)
.exists(_.values.exists(_ == CacheDirective.`no-store`)) &&
(resp.status match {
case Ok => true
case NonAuthoritativeInformation => true
case NoContent => true
case PartialContent => true
case MultipleChoices => true
case MovedPermanently => true
case NotFound => true
case MethodNotAllowed => true
case Gone => true
case UriTooLong => true
case NotImplemented => true
case _ => false
})
}
def caching: Middleware[F, Request[G], Response[G], Request[G], Response[G]] = http => Kleisli { request =>
getCached(request, http)
}
}
object HttpCaching {
def apply[F[_]: Sync, G[_]](
http: Http[F, G]
): Http[F, G] = new HttpCaching().caching(http)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment