Browse Source

Upgraded Http4s to 0.20.0-M3 (#4214)

* Upgraded http4s version, use cats and replaced circe with jsoniter-scala

* Improved response transformation

* Updating http4s to 0.20.0-M3
Laurent Bédubourg 6 years ago
parent
commit
d1f960b2d4

+ 4 - 20
frameworks/Scala/http4s/README.md

@@ -1,26 +1,10 @@
 # http4s Benchmarking Test
 # http4s Benchmarking Test
 
 
-### JSON Encoding Test
-
-* [JSON test source](src/main/scala/code/lib/WebServer.scala)
-
 ## Infrastructure Software Versions
 ## Infrastructure Software Versions
+
 The tests were run with:
 The tests were run with:
 
 
 * [Java Oracle 1.8](http://www.oracle.com/technetwork/java/javase)
 * [Java Oracle 1.8](http://www.oracle.com/technetwork/java/javase)
-* [http4s 0.15.9a](http://http4s.org/)
-* [blaze 0.12.4](https://github.com/http4s/blaze/)
-
-## Test URLs
-### JSON Encoding Test
-
-http://localhost:8080/json
-
-### Plaintext Test
-
-http://localhost:8080/plaintext
-
-## How to run
-sbt 'oneJar'
-
-java -jar target/scala-2.12/http4s_2.12-1.0-SNAPSHOT-one-jar.jar "$DBHOST"
+* [http4s 0.20.0-M3](http://http4s.org/)
+* [doobie 0.6.0](https://tpolecat.github.io/doobie/)
+* [blaze 0.14.0-M11](https://github.com/http4s/blaze/)

+ 2 - 3
frameworks/Scala/http4s/build.sbt

@@ -21,9 +21,8 @@ enablePlugins(SbtTwirl)
 
 
 TwirlKeys.templateImports += "http4s.techempower.benchmark._"
 TwirlKeys.templateImports += "http4s.techempower.benchmark._"
 
 
-val http4sVersion = "0.18.12"
-val circeVersion = "0.9.3"
-val doobieVersion = "0.5.3"
+val http4sVersion = "0.20.0-M3"
+val doobieVersion = "0.6.0"
 
 
 libraryDependencies ++= Seq(
 libraryDependencies ++= Seq(
   "org.http4s" %% "http4s-blaze-server" % http4sVersion,
   "org.http4s" %% "http4s-blaze-server" % http4sVersion,

+ 2 - 2
frameworks/Scala/http4s/http4s.dockerfile

@@ -1,7 +1,7 @@
-FROM hseeberger/scala-sbt:8u151-2.12.5-1.1.2
+FROM hseeberger/scala-sbt:8u181_2.12.7_1.2.6
 WORKDIR /http4s
 WORKDIR /http4s
 COPY project project
 COPY project project
 COPY src src
 COPY src src
 COPY build.sbt build.sbt
 COPY build.sbt build.sbt
 RUN sbt assembly -batch
 RUN sbt assembly -batch
-CMD ["java", "-jar", "target/scala-2.12/http4s-assembly-1.0.jar", "tfb-database"]
+CMD ["java", "-server", "-Xms2g", "-Xmx2g", "-XX:NewSize=1g", "-XX:MaxNewSize=1g", "-XX:InitialCodeCacheSize=256m", "-XX:ReservedCodeCacheSize=256m", "-XX:+UseParallelGC", "-XX:+UseNUMA", "-XX:+AggressiveOpts", "-XX:-UseBiasedLocking", "-XX:+AlwaysPreTouch", "-jar", "target/scala-2.12/http4s-assembly-1.0.jar", "tfb-database"]

+ 1 - 1
frameworks/Scala/http4s/project/build.properties

@@ -1 +1 @@
-sbt.version=1.1.2
+sbt.version=1.2.6

+ 82 - 74
frameworks/Scala/http4s/src/main/scala/WebServer.scala

@@ -5,7 +5,6 @@ import com.github.plokhotnyuk.jsoniter_scala.macros._
 
 
 import cats.effect._
 import cats.effect._
 import cats.implicits._
 import cats.implicits._
-import fs2.{StreamApp, Stream}
 
 
 import org.http4s._
 import org.http4s._
 import org.http4s.headers.`Content-Type`
 import org.http4s.headers.`Content-Type`
@@ -14,13 +13,12 @@ import org.http4s.server.blaze.BlazeBuilder
 import org.http4s.twirl._
 import org.http4s.twirl._
 
 
 import doobie.hikari.HikariTransactor
 import doobie.hikari.HikariTransactor
+import doobie.util.ExecutionContexts
 import doobie._
 import doobie._
 import doobie.implicits._
 import doobie.implicits._
 
 
 import java.util.concurrent.ThreadLocalRandom
 import java.util.concurrent.ThreadLocalRandom
 
 
-import scala.concurrent.ExecutionContext.Implicits.global
-
 case class Message(message: String)
 case class Message(message: String)
 case class World(id: Int, randomNumber: Int)
 case class World(id: Int, randomNumber: Int)
 case class Fortune(id: Int, message: String)
 case class Fortune(id: Int, message: String)
@@ -34,72 +32,71 @@ object Queries {
     })
     })
 }
 }
 
 
-object WebServer extends StreamApp[IO] with Http4sDsl[IO] {
-
+object JsonSupport {
   implicit val messageCodec: JsonValueCodec[Message] = JsonCodecMaker.make[Message](CodecMakerConfig())
   implicit val messageCodec: JsonValueCodec[Message] = JsonCodecMaker.make[Message](CodecMakerConfig())
   implicit val worldCodec: JsonValueCodec[World] = JsonCodecMaker.make[World](CodecMakerConfig())
   implicit val worldCodec: JsonValueCodec[World] = JsonCodecMaker.make[World](CodecMakerConfig())
   implicit val worldListCodec: JsonValueCodec[List[World]] = JsonCodecMaker.make[List[World]](CodecMakerConfig())
   implicit val worldListCodec: JsonValueCodec[List[World]] = JsonCodecMaker.make[List[World]](CodecMakerConfig())
   implicit val fortuneCodec: JsonValueCodec[Fortune] = JsonCodecMaker.make[Fortune](CodecMakerConfig())
   implicit val fortuneCodec: JsonValueCodec[Fortune] = JsonCodecMaker.make[Fortune](CodecMakerConfig())
 
 
-
   implicit def jsonEncoder[T: JsonValueCodec]: EntityEncoder[IO, T] =
   implicit def jsonEncoder[T: JsonValueCodec]: EntityEncoder[IO, T] =
     EntityEncoder
     EntityEncoder
       .byteArrayEncoder[IO]
       .byteArrayEncoder[IO]
       .contramap((data: T) => writeToArray(data))
       .contramap((data: T) => writeToArray(data))
-      .withContentType(`Content-Type`(MediaType.`application/json`, Some(Charset.`UTF-8`)))
+      .withContentType(`Content-Type`(MediaType.application.json, Some(Charset.`UTF-8`)))
+}
 
 
-  def addHeaders(service: HttpService[IO]): HttpService[IO] =
-    cats.data.Kleisli { req: Request[IO] =>
-      service.run(req).map(_.putHeaders(Header("Server", req.serverAddr)))
-    }
+object WebServer extends IOApp with Http4sDsl[IO] {
+  import JsonSupport._
 
 
-  def connectDatabase(host: String, poolSize: Int): IO[HikariTransactor[IO]] = {
-    val driver = "org.postgresql.Driver"
-    val url = s"jdbc:postgresql://$host/hello_world"
-    val user = "benchmarkdbuser"
-    val pass = "benchmarkdbpass"
-    val maxPoolSize = poolSize
-    val minIdle = poolSize
+  def openDatabase(host: String, poolSize: Int): Resource[IO, HikariTransactor[IO]] =
     for {
     for {
-      xa <- HikariTransactor.newHikariTransactor[IO](driver, url, user, pass)
-      _  <- xa.configure(ds => IO {
-         ds.setMaximumPoolSize(maxPoolSize)
-         ds.setMinimumIdle(minIdle)
-      })
+      ce <- ExecutionContexts.fixedThreadPool[IO](32) // our connect EC
+      te <- ExecutionContexts.cachedThreadPool[IO]    // our transaction TE
+      xa <- HikariTransactor.newHikariTransactor[IO](
+        "org.postgresql.Driver",
+        s"jdbc:postgresql://$host/hello_world",
+        "benchmarkdbuser",
+        "benchmarkdbpass",
+        ce,
+        te
+      )
+      _  <- Resource.liftF(
+        xa.configure(ds => IO {
+         ds.setMaximumPoolSize(poolSize)
+         ds.setMinimumIdle(poolSize)
+        })
+      )
     } yield xa
     } yield xa
-  }
 
 
   // Provide a random number between 1 and 10000 (inclusive)
   // Provide a random number between 1 and 10000 (inclusive)
   val randomWorldId: IO[Int] = IO(ThreadLocalRandom.current.nextInt(1, 10001))
   val randomWorldId: IO[Int] = IO(ThreadLocalRandom.current.nextInt(1, 10001))
 
 
   // Update the randomNumber field with a random number
   // Update the randomNumber field with a random number
-  def updateRandomNumber(world: World): IO[World] = {
-    randomWorldId map { id =>
+  def updateRandomNumber(world: World): IO[World] =
+    randomWorldId.map(id =>
       world.copy(randomNumber = id)
       world.copy(randomNumber = id)
-    }
-  }
+    )
 
 
   // Select a World object from the database by ID
   // Select a World object from the database by ID
-  def selectWorld(xa: Transactor[IO], id: Int): IO[World] = {
-    val query = sql"select id, randomNumber from World where id = $id".query[World]
-    query.unique.transact(xa)
-  }
+  def selectWorld(xa: Transactor[IO], id: Int): IO[World] =
+    sql"select id, randomNumber from World where id = $id"
+      .query[World]
+      .unique
+      .transact(xa)
 
 
   // Select a random World object from the database
   // Select a random World object from the database
-  def selectRandomWorld(xa: Transactor[IO]): IO[World] = {
+  def selectRandomWorld(xa: Transactor[IO]): IO[World] =
     randomWorldId flatMap { id =>
     randomWorldId flatMap { id =>
       selectWorld(xa, id)
       selectWorld(xa, id)
     }
     }
-  }
 
 
   // Select a specified number of random World objects from the database
   // Select a specified number of random World objects from the database
   def getWorlds(xa: Transactor[IO], numQueries: Int): IO[List[World]] =
   def getWorlds(xa: Transactor[IO], numQueries: Int): IO[List[World]] =
     (0 until numQueries).toList.traverse(_ => selectRandomWorld(xa))
     (0 until numQueries).toList.traverse(_ => selectRandomWorld(xa))
 
 
   // Update the randomNumber field with a new random number, for a list of World objects
   // Update the randomNumber field with a new random number, for a list of World objects
-  def getNewWorlds(worlds: List[World]): IO[List[World]] = {
+  def getNewWorlds(worlds: List[World]): IO[List[World]] =
     worlds.traverse(updateRandomNumber)
     worlds.traverse(updateRandomNumber)
-  }
 
 
   // Update the randomNumber column in the database for a specified set of World objects,
   // Update the randomNumber column in the database for a specified set of World objects,
   // this uses a batch update SQL call.
   // this uses a batch update SQL call.
@@ -121,52 +118,63 @@ object WebServer extends StreamApp[IO] with Http4sDsl[IO] {
     (newFortune :: old).sortBy(_.message)
     (newFortune :: old).sortBy(_.message)
   }
   }
 
 
+  // Add Server header container server address
+  def addServerHeader(service: HttpRoutes[IO]): HttpRoutes[IO] =
+    cats.data.Kleisli { req: Request[IO] =>
+      service.run(req).map(_.putHeaders(Header("Server", req.serverAddr)))
+    }
+
   // HTTP service definition
   // HTTP service definition
-  def service(xa: Transactor[IO]) = HttpService[IO] {
-    case GET -> Root / "json" =>
-      Ok(Message("Hello, World!"))
-
-    case GET -> Root / "db" =>
-      Ok(selectRandomWorld(xa))
-
-    case GET -> Root / "queries" :? Queries(numQueries) =>
-      Ok(getWorlds(xa, numQueries))
-
-    case GET -> Root / "fortunes" =>
-      val page = for {
-        oldFortunes <- getFortunes(xa)
-        newFortunes = getSortedFortunes(oldFortunes)
-      } yield html.index(newFortunes)
-      Ok(page)
-
-    case GET -> Root / "updates" :? Queries(numQueries) =>
-      val updated = for {
-        worlds <- getWorlds(xa, numQueries)
-        newWorlds <- getNewWorlds(worlds)
-        _ <- updateWorlds(xa, newWorlds)
-      } yield newWorlds
-      Ok(updated)
-
-    case GET -> Root / "plaintext" =>
-      Ok("Hello, World!")
-  }
+  def service(xa: Transactor[IO]) =
+    addServerHeader(
+      HttpRoutes.of[IO] {
+        case GET -> Root / "plaintext" =>
+          Ok("Hello, World!")
+
+        case GET -> Root / "json" =>
+          Ok(Message("Hello, World!"))
+
+        case GET -> Root / "db" =>
+          Ok(selectRandomWorld(xa))
+
+        case GET -> Root / "queries" :? Queries(numQueries) =>
+          Ok(getWorlds(xa, numQueries))
+
+        case GET -> Root / "fortunes" =>
+          Ok(
+            for {
+              oldFortunes <- getFortunes(xa)
+              newFortunes = getSortedFortunes(oldFortunes)
+            } yield html.index(newFortunes)
+          )
+
+        case GET -> Root / "updates" :? Queries(numQueries) =>
+          Ok(
+            for {
+              worlds <- getWorlds(xa, numQueries)
+              newWorlds <- getNewWorlds(worlds)
+              _ <- updateWorlds(xa, newWorlds)
+            } yield newWorlds
+          )
+      }
+    )
 
 
   // Given a fully constructed HttpService, start the server and wait for completion
   // Given a fully constructed HttpService, start the server and wait for completion
-  def startServer(service: HttpService[IO]) = {
+  def startServer(service: HttpRoutes[IO]) =
     BlazeBuilder[IO]
     BlazeBuilder[IO]
       .bindHttp(8080, "0.0.0.0")
       .bindHttp(8080, "0.0.0.0")
       .mountService(service, "/")
       .mountService(service, "/")
-      .serve
-  }
+      .resource
 
 
   // Entry point when starting service
   // Entry point when starting service
-  override def stream(args: List[String], requestShutdown: IO[Unit]) = {
-    for {
-      xa <- Stream.eval(connectDatabase(
+  override def run(args: List[String]): IO[ExitCode] =
+    (for {
+      db <- openDatabase(
         args.headOption.getOrElse("localhost"),
         args.headOption.getOrElse("localhost"),
         sys.env.get("DB_POOL_SIZE").map(_.toInt).getOrElse(256)
         sys.env.get("DB_POOL_SIZE").map(_.toInt).getOrElse(256)
-      ))
-      exitCode <- startServer(addHeaders(service(xa)))
-    } yield exitCode
-  }
+      )
+      server <- startServer(service(db))
+    } yield server)
+      .use(_ => IO.never)
+      .map(_ => ExitCode.Success)
 }
 }