Browse Source

Use Hexagon 2.0 and replace Resin with Tomcat (#7016)

* Fix error with URLs in JEE servers

* Clean up

* Avoid classpath URLs

* Fix template loading error

* Fix template loading error

* Chores

* Fix template loading error

* Delete MongoDB DB support

Storage support in Hexagon will be moved outside the Toolkit, and so, it will be left outside the benchmark.

* Fix runtime problem

* Update Hexagon version

* Make Jackson Blackbird module optional

* Add variation with Blackbird module enabled

* Upgrade Hexagon version

* Enable blackbird Jackson module by default

* Update dependencies

* Use Hexagon version 2.0.0-B1 (and a little cleanup)

* Use Hexagon version 2.0.0-B1 (and a little cleanup)

* Use Tomcat instead Resin to test JEE integration

* Remove unused environment variable

* Clean Tomcat dockerfile

* Minor improvements

* Minor improvements

* Update to release version
Juanjo Aguililla 3 years ago
parent
commit
61c2e79c39

+ 1 - 1
frameworks/Kotlin/hexagon/README.md

@@ -31,7 +31,7 @@ and `${TEMPLATE_ENGINE}` with: `pebble`
 * Database updates: http://localhost:9090/${DB_ENGINE}/update
 * Database queries: http://localhost:9090/${DB_ENGINE}/query
 
-### Resin
+### Tomcat
 
 * JSON Encoding Test: http://localhost:8080/json
 * Plain Text Test: http://localhost:8080/plaintext

+ 2 - 2
frameworks/Kotlin/hexagon/benchmark_config.json

@@ -25,7 +25,7 @@
                 "notes": "http://hexagonkt.com",
                 "versus": "servlet"
             },
-            "resin": {
+            "tomcat": {
                 "json_url": "/json",
                 "db_url": "/postgresql/db",
                 "query_url": "/postgresql/query?queries=",
@@ -44,7 +44,7 @@
                 "webserver": "None",
                 "os": "Linux",
                 "database_os": "Linux",
-                "display_name": "Hexagon Resin PostgreSQL",
+                "display_name": "Hexagon Tomcat PostgreSQL",
                 "notes": "http://hexagonkt.com",
                 "versus": "servlet"
             }

+ 4 - 6
frameworks/Kotlin/hexagon/build.gradle

@@ -4,14 +4,13 @@ plugins {
 }
 
 ext {
-    gradleScripts = "https://raw.githubusercontent.com/hexagonkt/hexagon/1.5.1/gradle"
+    gradleScripts = "https://raw.githubusercontent.com/hexagonkt/hexagon/2.0.0/gradle"
 
-    hexagonVersion = "1.5.1"
+    hexagonVersion = "2.0.0"
     hikariVersion = "5.0.0"
-    jettyVersion = "10.0.7"
+    jettyVersion = "11.0.7"
     postgresqlVersion = "42.3.1"
     cache2kVersion = "2.4.1.Final"
-    jacksonBlackbirdVersion = "2.13.0"
 }
 
 apply(from: "$gradleScripts/kotlin.gradle")
@@ -35,9 +34,8 @@ dependencies {
     implementation("com.hexagonkt:http_server_jetty:$hexagonVersion")
     implementation("com.hexagonkt:templates_pebble:$hexagonVersion")
     implementation("com.hexagonkt:logging_slf4j_jul:$hexagonVersion")
-    implementation("com.hexagonkt:serialization_json:$hexagonVersion")
+    implementation("com.hexagonkt:serialization_jackson_json:$hexagonVersion")
 
-    implementation("com.fasterxml.jackson.module:jackson-module-blackbird:$jacksonBlackbirdVersion")
     implementation("org.cache2k:cache2k-core:$cache2kVersion")
     implementation("com.zaxxer:HikariCP:$hikariVersion")
     implementation("org.postgresql:postgresql:$postgresqlVersion")

+ 1 - 1
frameworks/Kotlin/hexagon/config.toml

@@ -19,7 +19,7 @@ platform = "Servlet"
 webserver = "None"
 versus = "servlet"
 
-[resin]
+[tomcat]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
 urls.db = "/postgresql/db"

+ 0 - 26
frameworks/Kotlin/hexagon/hexagon-resin.dockerfile

@@ -1,26 +0,0 @@
-#
-# BUILD
-#
-FROM gradle:7.2-jdk11 AS gradle_build
-USER root
-WORKDIR /hexagon
-
-COPY src src
-COPY build.gradle build.gradle
-RUN gradle --quiet
-
-#
-# RUNTIME
-#
-FROM adoptopenjdk:11-jre-hotspot-bionic
-ENV DBSTORE postgresql
-ENV POSTGRESQL_DB_HOST tfb-database
-ENV RESIN http://caucho.com/download/resin-4.0.65.tar.gz
-
-WORKDIR /resin
-RUN curl -sL $RESIN | tar xz --strip-components=1
-RUN rm -rf webapps/*
-COPY --from=gradle_build /hexagon/build/libs/ROOT.war webapps/ROOT.war
-COPY src/main/resources/fortunes.pebble.html fortunes.pebble.html
-EXPOSE 8080
-CMD ["java", "-jar", "lib/resin.jar", "console"]

+ 20 - 0
frameworks/Kotlin/hexagon/hexagon-tomcat.dockerfile

@@ -0,0 +1,20 @@
+#
+# BUILD
+#
+FROM gradle:7.2-jdk11 AS gradle_build
+USER root
+WORKDIR /hexagon
+
+COPY src src
+COPY build.gradle build.gradle
+RUN gradle --quiet
+
+#
+# RUNTIME
+#
+FROM tomcat:10.0.14-jre17-temurin
+ENV DBSTORE postgresql
+ENV POSTGRESQL_DB_HOST tfb-database
+
+COPY --from=gradle_build /hexagon/build/libs/ROOT.war /usr/local/tomcat/webapps/ROOT.war
+EXPOSE 8080

+ 0 - 1
frameworks/Kotlin/hexagon/hexagon.dockerfile

@@ -17,7 +17,6 @@ ENV DBSTORE postgresql
 ENV POSTGRESQL_DB_HOST tfb-database
 ENV WEBENGINE jetty
 ENV PROJECT hexagon
-ENV ENABLE_BLACKBIRD true
 
 COPY --from=gradle_build /hexagon/build/install/$PROJECT /opt/$PROJECT
 

+ 18 - 21
frameworks/Kotlin/hexagon/src/main/kotlin/Benchmark.kt

@@ -1,47 +1,44 @@
 package com.hexagonkt
 
-import com.fasterxml.jackson.module.blackbird.BlackbirdModule
-import com.hexagonkt.core.helpers.Jvm
-import com.hexagonkt.http.server.Server
-import com.hexagonkt.http.server.ServerPort
-import com.hexagonkt.http.server.ServerSettings
+import com.hexagonkt.http.server.HttpServer
+import com.hexagonkt.http.server.HttpServerPort
+import com.hexagonkt.http.server.HttpServerSettings
 import com.hexagonkt.http.server.jetty.JettyServletAdapter
-import com.hexagonkt.serialization.*
-import com.hexagonkt.serialization.json.*
 import com.hexagonkt.store.BenchmarkSqlStore
 import com.hexagonkt.store.BenchmarkStore
 import com.hexagonkt.templates.TemplatePort
 import com.hexagonkt.templates.pebble.PebbleAdapter
 import java.net.InetAddress
 
-internal val benchmarkStores: Map<String, BenchmarkStore> by lazy {
+internal val stores: Map<String, BenchmarkStore> by lazy {
     mapOf("postgresql" to BenchmarkSqlStore("postgresql"))
 }
 
-internal val benchmarkTemplateEngines: Map<String, TemplatePort> by lazy {
+internal val templateEngines: Map<String, TemplatePort> by lazy {
     mapOf("pebble" to PebbleAdapter)
 }
 
-internal val benchmarkEngines: Map<String, ServerPort> by lazy {
+private val engines: Map<String, HttpServerPort> by lazy {
     mapOf("jetty" to JettyServletAdapter())
 }
 
-internal val benchmarkServer: Server by lazy {
+private val server: HttpServer by lazy {
     val settings = Settings()
-    val engine = benchmarkEngines[settings.webEngine] ?: error("Unsupported server engine")
-    val serverSettings = ServerSettings(
+    val engine = engines[settings.webEngine] ?: error("Unsupported server engine")
+    val controller = Controller(settings, stores, templateEngines)
+    val serverSettings = HttpServerSettings(
         bindAddress = InetAddress.getByName(settings.bindAddress),
-        bindPort = settings.bindPort
+        bindPort = settings.bindPort,
+        options = mapOf(
+            "sendDateHeader" to settings.sendDateHeader,
+            "sendServerVersion" to settings.sendServerVersion,
+            "sendXPoweredBy" to settings.sendXPoweredBy,
+        ),
     )
 
-    Server(engine, Controller(settings).router, serverSettings)
+    HttpServer(engine, controller.path, serverSettings)
 }
 
 fun main() {
-    if (Jvm.systemFlag("ENABLE_BLACKBIRD"))
-        Json.mapper.registerModule(BlackbirdModule())
-    SerializationManager.mapper = JacksonMapper
-    SerializationManager.formats = linkedSetOf(Json)
-
-    benchmarkServer.start()
+    server.start()
 }

+ 76 - 52
frameworks/Kotlin/hexagon/src/main/kotlin/Controller.kt

@@ -1,78 +1,110 @@
 package com.hexagonkt
 
-import com.hexagonkt.core.helpers.require
-import com.hexagonkt.http.server.Call
-import com.hexagonkt.http.server.Router
-import com.hexagonkt.serialization.json.Json
-import com.hexagonkt.serialization.toFieldsMap
+import com.hexagonkt.core.require
+import com.hexagonkt.core.media.ApplicationMedia.JSON
+import com.hexagonkt.core.media.TextMedia.HTML
+import com.hexagonkt.core.media.TextMedia.PLAIN
+import com.hexagonkt.http.model.ContentType
+import com.hexagonkt.http.server.handlers.HttpServerContext
+import com.hexagonkt.http.server.handlers.PathHandler
+import com.hexagonkt.http.server.handlers.path
+import com.hexagonkt.serialization.jackson.json.Json
+import com.hexagonkt.serialization.serialize
 import com.hexagonkt.store.BenchmarkStore
 import com.hexagonkt.templates.TemplatePort
+
 import java.net.URL
 import java.util.concurrent.ThreadLocalRandom
 
-class Controller(private val settings: Settings) {
+import kotlin.text.Charsets.UTF_8
 
-    private val templates: Map<String, URL> = mapOf(
-        "pebble" to (urlOrNull("classpath:fortunes.pebble.html") ?: URL("file:/resin/fortunes.pebble.html"))
-    )
-
-    internal val router: Router by lazy {
-        Router {
-            before {
-                response.headers["Server"] = "Servlet/3.1"
-                response.headers["Transfer-Encoding"] = "chunked"
-            }
+class Controller(
+    settings: Settings,
+    stores: Map<String, BenchmarkStore>,
+    templateEngines: Map<String, TemplatePort>,
+) {
+    private val queriesParam: String = settings.queriesParam
+    private val cachedQueriesParam: String = settings.cachedQueriesParam
+    private val worldRows: Int = settings.worldRows
 
-            get("/plaintext") { ok(settings.textMessage, "text/plain") }
-            get("/json") { ok(Message(settings.textMessage), Json) }
+    private val plain: ContentType = ContentType(PLAIN)
+    private val json: ContentType = ContentType(JSON)
+    private val html: ContentType = ContentType(HTML, charset = UTF_8)
 
-            benchmarkStores.forEach { (storeEngine, store) ->
-                benchmarkTemplateEngines.forEach { (templateEngineId, templateEngine) ->
-                    val path = "/$storeEngine/${templateEngineId}/fortunes"
+    private val templates: Map<String, URL> = mapOf(
+        "pebble" to URL("classpath:fortunes.pebble.html")
+    )
 
-                    get(path) { listFortunes(store, templateEngineId, templateEngine) }
+    internal val path: PathHandler by lazy {
+        path {
+            get("/plaintext") { ok(settings.textMessage, contentType = plain) }
+            get("/json") { ok(Message(settings.textMessage).serialize(Json.raw), contentType = json) }
+
+            stores.forEach { (storeEngine, store) ->
+                path("/$storeEngine") {
+                    templateEngines.forEach { (templateEngineId, templateEngine) ->
+                        get("/${templateEngineId}/fortunes") { listFortunes(store, templateEngineId, templateEngine) }
+                    }
+
+                    get("/db") { dbQuery(store) }
+                    get("/query") { getWorlds(store) }
+                    get("/cached") { getCachedWorlds(store) }
+                    get("/update") { updateWorlds(store) }
                 }
-
-                get("/$storeEngine/db") { dbQuery(store) }
-                get("/$storeEngine/query") { getWorlds(store) }
-                get("/$storeEngine/cached") { getCachedWorlds(store) }
-                get("/$storeEngine/update") { updateWorlds(store) }
             }
         }
     }
 
-    private fun Call.listFortunes(store: BenchmarkStore, templateKind: String, templateAdapter: TemplatePort) {
+    private fun HttpServerContext.listFortunes(
+        store: BenchmarkStore, templateKind: String, templateAdapter: TemplatePort
+    ): HttpServerContext {
 
         val fortunes = store.findAllFortunes() + Fortune(0, "Additional fortune added at request time.")
         val sortedFortunes = fortunes.sortedBy { it.message }
         val context = mapOf("fortunes" to sortedFortunes)
+        val body = templateAdapter.render(templates.require(templateKind), context)
 
-        response.contentType = "text/html;charset=utf-8"
-        ok(templateAdapter.render(templates.require(templateKind), context))
+        return ok(body, contentType = html)
     }
 
-    private fun Call.dbQuery(store: BenchmarkStore) {
-        ok(store.findWorlds(listOf(randomWorld())).first(), Json)
+    private fun HttpServerContext.dbQuery(store: BenchmarkStore): HttpServerContext {
+        val ids = listOf(randomWorld())
+        val worlds = store.findWorlds(ids)
+        val world = worlds.first()
+
+        return sendJson(world)
     }
 
-    private fun Call.getWorlds(store: BenchmarkStore) {
-        val ids = (1..getWorldsCount(settings.queriesParam)).map { randomWorld() }
-        ok(store.findWorlds(ids), Json)
+    private fun HttpServerContext.getWorlds(store: BenchmarkStore): HttpServerContext {
+        val worldsCount = getWorldsCount(queriesParam)
+        val ids = (1..worldsCount).map { randomWorld() }
+        val worlds = store.findWorlds(ids)
+
+        return sendJson(worlds)
     }
 
-    private fun Call.getCachedWorlds(store: BenchmarkStore) {
-        val ids = (1..getWorldsCount(settings.cachedQueriesParam)).map { randomWorld() }
-        ok(store.findCachedWorlds(ids).map { it.toFieldsMap() }, Json)
+    private fun HttpServerContext.getCachedWorlds(store: BenchmarkStore): HttpServerContext {
+        val worldsCount = getWorldsCount(cachedQueriesParam)
+        val ids = (1..worldsCount).map { randomWorld() }
+        val worlds = store.findCachedWorlds(ids)
+
+        return sendJson(worlds)
     }
 
-    private fun Call.updateWorlds(store: BenchmarkStore) {
-        val worlds = (1..getWorldsCount(settings.queriesParam)).map { World(randomWorld(), randomWorld()) }
+    private fun HttpServerContext.updateWorlds(store: BenchmarkStore): HttpServerContext {
+        val worldsCount = getWorldsCount(queriesParam)
+        val worlds = (1..worldsCount).map { World(randomWorld(), randomWorld()) }
+
         store.replaceWorlds(worlds)
-        ok(worlds, Json)
+
+        return sendJson(worlds)
     }
 
-    private fun Call.getWorldsCount(parameter: String): Int =
-        queryParametersValues[parameter]?.firstOrNull()?.toIntOrNull().let {
+    private fun HttpServerContext.sendJson(body: Any): HttpServerContext =
+        ok(body.serialize(Json.raw), contentType = json)
+
+    private fun HttpServerContext.getWorldsCount(parameter: String): Int =
+        request.queryParameters[parameter]?.toIntOrNull().let {
             when {
                 it == null -> 1
                 it < 1 -> 1
@@ -82,13 +114,5 @@ class Controller(private val settings: Settings) {
         }
 
     private fun randomWorld(): Int =
-        ThreadLocalRandom.current().nextInt(settings.worldRows) + 1
-
-    private fun urlOrNull(path: String): URL? =
-        try {
-            URL(path)
-        }
-        catch (e: Exception) {
-            null
-        }
+        ThreadLocalRandom.current().nextInt(worldRows) + 1
 }

+ 6 - 1
frameworks/Kotlin/hexagon/src/main/kotlin/Settings.kt

@@ -1,6 +1,6 @@
 package com.hexagonkt
 
-import com.hexagonkt.core.helpers.Jvm.systemSetting
+import com.hexagonkt.core.Jvm.systemSetting
 
 data class Settings(
     val bindPort: Int = systemSetting("bindPort") ?: 9090,
@@ -20,6 +20,11 @@ data class Settings(
     val worldName: String = systemSetting("worldCollection") ?: "world",
     val fortuneName: String = systemSetting("fortuneCollection") ?: "fortune",
     val databaseName: String = systemSetting("database") ?: "hello_world",
+    val databaseDriver: String = systemSetting("databaseDriver") ?: "org.postgresql.Driver",
+
+    val sendDateHeader: Boolean = systemSetting("sendDateHeader") ?: true,
+    val sendServerVersion: Boolean = systemSetting("sendServerVersion") ?: true,
+    val sendXPoweredBy: Boolean = systemSetting("sendXPoweredBy") ?: false,
 
     val worldRows: Int = 10_000,
     val textMessage: String = "Hello, World!",

+ 16 - 10
frameworks/Kotlin/hexagon/src/main/kotlin/WebListenerServer.kt

@@ -1,17 +1,23 @@
 package com.hexagonkt
 
-import com.fasterxml.jackson.module.blackbird.BlackbirdModule
+import com.hexagonkt.core.multiMapOf
+import com.hexagonkt.http.server.handlers.HttpHandler
+import com.hexagonkt.http.server.handlers.OnHandler
 import com.hexagonkt.http.server.servlet.ServletServer
-import com.hexagonkt.serialization.json.JacksonMapper
-import com.hexagonkt.serialization.json.Json
-import com.hexagonkt.serialization.SerializationManager
-import javax.servlet.annotation.WebListener
+import jakarta.servlet.annotation.WebListener
 
-@WebListener class WebListenerServer(settings: Settings = Settings()) : ServletServer(Controller(settings).router) {
+@WebListener class WebListenerServer(settings: Settings = Settings()) : ServletServer(createHandlers(settings)) {
 
-    init {
-        Json.mapper.registerModule(BlackbirdModule())
-        SerializationManager.mapper = JacksonMapper
-        SerializationManager.formats = linkedSetOf(Json)
+    private companion object {
+
+        fun createHandlers(settings: Settings): List<HttpHandler> {
+            val controller = Controller(settings, stores, templateEngines)
+            val controllerPath = controller.path
+            val serverHeaderHandler = OnHandler("*") {
+                send(headers = multiMapOf("server" to "Tomcat"))
+            }
+
+            return listOf(serverHeaderHandler, controllerPath)
+        }
     }
 }

+ 4 - 3
frameworks/Kotlin/hexagon/src/main/kotlin/store/BenchmarkSqlStore.kt

@@ -4,7 +4,7 @@ import com.hexagonkt.CachedWorld
 import com.hexagonkt.Fortune
 import com.hexagonkt.Settings
 import com.hexagonkt.World
-import com.hexagonkt.core.helpers.Jvm
+import com.hexagonkt.core.Jvm
 import com.zaxxer.hikari.HikariConfig
 import com.zaxxer.hikari.HikariDataSource
 import org.cache2k.Cache
@@ -27,8 +27,9 @@ internal class BenchmarkSqlStore(engine: String, private val settings: Settings
         val config = HikariConfig().apply {
             jdbcUrl = "jdbc:postgresql://$dbHost/${settings.databaseName}"
             maximumPoolSize = Jvm.systemSetting(Int::class, "maximumPoolSize") ?: poolSize
-            username = Jvm.systemSetting("databaseUsername") ?: "benchmarkdbuser"
-            password = Jvm.systemSetting("databasePassword") ?: "benchmarkdbpass"
+            driverClassName = settings.databaseDriver
+            username = settings.databaseUsername
+            password = settings.databasePassword
         }
         HikariDataSource(config)
     }

+ 2 - 2
frameworks/Kotlin/hexagon/src/main/kotlin/store/BenchmarkStore.kt

@@ -7,7 +7,7 @@ import com.hexagonkt.World
 import org.cache2k.Cache
 import org.cache2k.Cache2kBuilder
 
-internal abstract class BenchmarkStore(settings: Settings) {
+abstract class BenchmarkStore(settings: Settings) {
 
     abstract fun findAllFortunes(): List<Fortune>
     abstract fun findWorlds(ids: List<Int>): List<World>
@@ -30,4 +30,4 @@ internal abstract class BenchmarkStore(settings: Settings) {
     fun findCachedWorlds(ids: List<Int>): List<CachedWorld> {
         return ids.mapNotNull { worldsCache.get(it) }
     }
-}
+}