diff --git a/build.sbt b/build.sbt index 6c27e9d..f7d005a 100644 --- a/build.sbt +++ b/build.sbt @@ -15,6 +15,7 @@ val circeVersion = "0.14.6" val fs2Version = "3.7.0" val http4sVersion = "0.23.23" val logbackVersion = "1.4.11" +val scaffeineVersion = "5.2.1" val scalamockVersion = "5.2.0" val scalatestVersion = "3.2.17" val shapelessVersion = "2.3.10" @@ -23,28 +24,29 @@ val snakeYamlVersion = "2.2" val vaultVersion = "3.5.0" libraryDependencies ++= Seq( - "org.scala-lang" % "scala-library" % scalaVersion.value % "provided", - "ch.qos.logback" % "logback-classic" % logbackVersion % Runtime, - "org.slf4j" % "slf4j-api" % slf4jVersion, - "org.http4s" %% "http4s-ember-client" % http4sVersion, - "org.http4s" %% "http4s-circe" % http4sVersion, - "org.http4s" %% "http4s-client" % http4sVersion, - "org.http4s" %% "http4s-core" % http4sVersion, - "co.fs2" %% "fs2-core" % fs2Version, - "co.fs2" %% "fs2-io" % fs2Version, - "com.chuusai" %% "shapeless" % shapelessVersion, - "io.circe" %% "circe-core" % circeVersion, - "org.typelevel" %% "case-insensitive" % caseInsensitiveVersion, - "org.typelevel" %% "cats-core" % catsCoreVersion, - "org.typelevel" %% "cats-effect" % catsEffectVersion, - "org.typelevel" %% "cats-effect-kernel" % catsEffectKernelVersion, - "org.typelevel" %% "vault" % vaultVersion, - "io.circe" %% "circe-generic" % circeVersion, - "io.circe" %% "circe-generic-extras" % circeGenericExtrasVersion, - "org.yaml" % "snakeyaml" % snakeYamlVersion, - "io.circe" %% "circe-parser" % circeVersion % Test, - "org.scalamock" %% "scalamock" % scalamockVersion % Test, - "org.scalatest" %% "scalatest" % scalatestVersion % Test + "org.scala-lang" % "scala-library" % scalaVersion.value % "provided", + "ch.qos.logback" % "logback-classic" % logbackVersion % Runtime, + "org.slf4j" % "slf4j-api" % slf4jVersion, + "org.http4s" %% "http4s-ember-client" % http4sVersion, + "org.http4s" %% "http4s-circe" % http4sVersion, + "org.http4s" %% "http4s-client" % http4sVersion, + "org.http4s" %% "http4s-core" % http4sVersion, + "co.fs2" %% "fs2-core" % fs2Version, + "co.fs2" %% "fs2-io" % fs2Version, + "com.chuusai" %% "shapeless" % shapelessVersion, + "io.circe" %% "circe-core" % circeVersion, + "org.typelevel" %% "case-insensitive" % caseInsensitiveVersion, + "org.typelevel" %% "cats-core" % catsCoreVersion, + "org.typelevel" %% "cats-effect" % catsEffectVersion, + "org.typelevel" %% "cats-effect-kernel" % catsEffectKernelVersion, + "org.typelevel" %% "vault" % vaultVersion, + "io.circe" %% "circe-generic" % circeVersion, + "io.circe" %% "circe-generic-extras" % circeGenericExtrasVersion, + "org.yaml" % "snakeyaml" % snakeYamlVersion, + "com.github.blemale" %% "scaffeine" % scaffeineVersion % "compile", + "io.circe" %% "circe-parser" % circeVersion % Test, + "org.scalamock" %% "scalamock" % scalamockVersion % Test, + "org.scalatest" %% "scalatest" % scalatestVersion % Test ) enablePlugins(JavaAppPackaging) diff --git a/src/main/scala/http/HttpClient.scala b/src/main/scala/http/HttpClient.scala index 9e425d9..21c0e7a 100644 --- a/src/main/scala/http/HttpClient.scala +++ b/src/main/scala/http/HttpClient.scala @@ -1,25 +1,47 @@ package http import cats.effect.IO +import cats.effect.unsafe.implicits.global import io.circe.Json import org.http4s.circe._ import org.http4s.client.middleware.FollowRedirect import org.http4s.ember.client.EmberClientBuilder import org.http4s.{Header, Method, Request, Uri} import org.typelevel.ci.CIString +import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine} + +import scala.concurrent.duration._ class HttpClient { - val client = EmberClientBuilder + private val client = EmberClientBuilder .default[IO] .build .map(FollowRedirect(5)) + private val cacheTtl = 5.seconds + + private val cache: AsyncLoadingCache[(Method, Uri, Option[String], Option[Json]), Either[Throwable, Json]] = + Scaffeine() + .recordStats() + .expireAfterWrite(cacheTtl) + .maximumSize(1000) + .buildAsyncFuture { case (method, url, apiKey, payload) => + makeHttpRequest(method, url, apiKey, payload).unsafeToFuture() + } + def httpRequest( method: Method, url: Uri, apiKey: Option[String] = None, payload: Option[Json] = None + ): IO[Either[Throwable, Json]] = IO.fromFuture(IO(cache.get(method, url, apiKey, payload))) + + private def makeHttpRequest( + method: Method, + url: Uri, + apiKey: Option[String] = None, + payload: Option[Json] = None ): IO[Either[Throwable, Json]] = { val host = s"${url.host.getOrElse(Uri.Host.unsafeFromString("127.0.0.1")).value}"