浏览代码

Fix http4k setup files, and add undertow server backend, database and fortunes tests (#2822)

* adding undertow implementation

* fixes to setup scripts

* fixes to setup scripts

* fixes to setup scripts

* fixes to setup scripts

* fixes to setup scripts

* fixes to setup scripts

* upgrade version of http4k

* adding fortunes and worlds tests

* some fixes for database driver

* fix number of records returned

* set content type on view

* set content type on json

* fix max and min

* adding new fortune to list and sorting result
David Denton 8 年之前
父节点
当前提交
169900b62b

+ 1 - 1
frameworks/Kotlin/http4k/Dockerfile

@@ -1,3 +1,3 @@
-FROM mysql
+FROM postgres
 
 ADD create.sql /docker-entrypoint-initdb.d/

+ 5 - 0
frameworks/Kotlin/http4k/README.md

@@ -11,6 +11,11 @@ The tests were run with:
 - JSON Encoding: http://localhost:9000/json
 - Plaintext: http://localhost:9000/plaintext
 
+## Supported backends
+- Jetty
+- Netty
+- Undertow
+
 ## How to run
 ```bash
 ./gradlew clean build jetty

+ 40 - 7
frameworks/Kotlin/http4k/benchmark_config.json

@@ -3,18 +3,22 @@
   "tests": [
     {
       "default": {
+        "setup_file": "setup_jetty",
         "orm": "Raw",
         "database_os": "Linux",
-        "setup_file": "setup_jetty",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "database": "postgres",
         "json_url": "/json",
         "plaintext_url": "/plaintext",
         "port": 9000,
         "approach": "Realistic",
         "classification": "Micro",
-        "database": "None",
         "framework": "http4k",
         "language": "Kotlin",
-        "platform": "Servlet",
+        "platform": "servlet",
         "webserver": "None",
         "os": "Linux",
         "display_name": "http4k-jetty",
@@ -23,25 +27,54 @@
       }
     },
     {
-      "default": {
+      "netty": {
+        "setup_file": "setup_netty",
         "orm": "Raw",
         "database_os": "Linux",
-        "setup_file": "setup_netty",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "database": "postgres",
         "json_url": "/json",
         "plaintext_url": "/plaintext",
         "port": 9000,
         "approach": "Realistic",
         "classification": "Micro",
-        "database": "None",
         "framework": "http4k",
         "language": "Kotlin",
-        "platform": "Netty",
+        "platform": "netty",
         "webserver": "None",
         "os": "Linux",
         "display_name": "http4k-netty",
         "notes": "",
         "versus": "netty"
       }
+    },
+    {
+      "undertow": {
+        "setup_file": "setup_undertow",
+        "orm": "Raw",
+        "database_os": "Linux",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "database": "postgres",
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 9000,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "framework": "http4k",
+        "language": "Kotlin",
+        "platform": "undertow",
+        "webserver": "None",
+        "os": "Linux",
+        "display_name": "http4k-undertow",
+        "notes": "",
+        "versus": "undertow"
+      }
     }
   ]
 }

+ 10 - 4
frameworks/Kotlin/http4k/build.gradle

@@ -1,6 +1,6 @@
 buildscript {
-    ext.kotlin_version = "1.1.2"
-    ext.http4k_version = "0.20.0"
+    ext.kotlin_version = "1.1.2-4"
+    ext.http4k_version = "1.23.0"
     repositories {
         mavenCentral()
         jcenter()
@@ -24,13 +24,16 @@ sourceCompatibility = JavaVersion.VERSION_1_8
 targetCompatibility = JavaVersion.VERSION_1_8
 
 dependencies {
-    compile "org.apache.commons:commons-lang3:3.5"
     compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     compile "org.http4k:http4k-core:$http4k_version"
-    compile "org.http4k:http4k-contract:$http4k_version"
     compile "org.http4k:http4k-format-jackson:$http4k_version"
     compile "org.http4k:http4k-server-jetty:$http4k_version"
     compile "org.http4k:http4k-server-netty:$http4k_version"
+    compile "org.http4k:http4k-server-undertow:$http4k_version"
+    compile "org.http4k:http4k-template-handlebars:$http4k_version"
+    compile "org.apache.commons:commons-lang3:3.5"
+    compile "com.zaxxer:HikariCP:2.6.1"
+    compile "org.postgresql:postgresql:42.0.0"
 }
 
 task jetty(type: OneJar) {
@@ -39,4 +42,7 @@ task jetty(type: OneJar) {
 
 task netty(type: OneJar) {
     mainClass = 'Http4kNettyServerKt'
+}
+task undertow(type: OneJar) {
+    mainClass = 'Http4kUndertowServerKt'
 }

+ 2 - 3
frameworks/Kotlin/http4k/setup_jetty.sh

@@ -1,8 +1,7 @@
 #!/bin/bash
 
-fw_depends mysql java
+fw_depends postgresql java
 
-gradle wrapper
+./gradlew clean build jetty
 
-gradle clean build jetty
 java -server -XX:+UseNUMA -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+AlwaysPreTouch -jar build/libs/http4k-standalone.jar &

+ 2 - 3
frameworks/Kotlin/http4k/setup_netty.sh

@@ -1,8 +1,7 @@
 #!/bin/bash
 
-fw_depends mysql java
+fw_depends postgresql java
 
-gradle wrapper
+./gradlew clean build netty
 
-gradle clean build netty
 java -server -XX:+UseNUMA -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+AlwaysPreTouch -jar build/libs/http4k-standalone.jar &

+ 7 - 0
frameworks/Kotlin/http4k/setup_undertow.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends postgresql java
+
+./gradlew clean build undertow
+
+java -server -XX:+UseNUMA -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+AlwaysPreTouch -jar build/libs/http4k-standalone.jar &

+ 9 - 4
frameworks/Kotlin/http4k/source_code

@@ -1,4 +1,9 @@
-http4k/src/main/scala/
-http4k/src/main/scala/Http4kBenchmarkServer.kt
-http4k/src/main/scala/Http4kJettyServer.kt
-http4k/src/main/scala/Http4kNettyServer.kt
+http4k/src/main/kotlin/Database.kt
+http4k/src/main/kotlin/FortunesRoute.kt
+http4k/src/main/kotlin/JsonRoute.kt
+http4k/src/main/kotlin/PlainTextRoute.kt
+http4k/src/main/kotlin/WorldRoutes.kt
+http4k/src/main/kotlin/Http4kBenchmarkServer.kt
+http4k/src/main/kotlin/Http4kJettyServer.kt
+http4k/src/main/kotlin/Http4kNettyServer.kt
+http4k/src/main/kotlin/Http4kUndertowServer.kt

+ 50 - 0
frameworks/Kotlin/http4k/src/main/kotlin/Database.kt

@@ -0,0 +1,50 @@
+import com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
+import java.sql.Connection
+import java.sql.ResultSet
+
+class Database(private val dataSource: javax.sql.DataSource) {
+
+    companion object {
+        operator fun invoke(host: String): Database {
+            val postgresqlUrl = "jdbc:postgresql://$host/hello_world?" +
+                "jdbcCompliantTruncation=false&" +
+                "elideSetAutoCommits=true&" +
+                "useLocalSessionState=true&" +
+                "cachePrepStmts=true&" +
+                "cacheCallableStmts=true&" +
+                "alwaysSendSetIsolation=false&" +
+                "prepStmtCacheSize=4096&" +
+                "cacheServerConfiguration=true&" +
+                "prepStmtCacheSqlLimit=2048&" +
+                "traceProtocol=false&" +
+                "useUnbufferedInput=false&" +
+                "useReadAheadInput=false&" +
+                "maintainTimeStats=false&" +
+                "useServerPrepStmts=true&" +
+                "cacheRSMetadata=true"
+
+            val config = HikariConfig()
+            config.jdbcUrl = postgresqlUrl
+            config.maximumPoolSize = 256
+            config.username = "benchmarkdbuser"
+            config.password = "benchmarkdbpass"
+            return Database(HikariDataSource(config))
+        }
+    }
+
+    fun <T> withConnection(fn: (Connection) -> T): T =
+        try {
+            fn(dataSource.connection)
+        } finally {
+            dataSource.connection.close()
+        }
+}
+
+fun <T> ResultSet.toList(fn: (ResultSet) -> T): List<T> {
+    val t = mutableListOf<T>()
+    while (this.next()) {
+        t.add(fn(this))
+    }
+    return t
+}

+ 31 - 0
frameworks/Kotlin/http4k/src/main/kotlin/FortunesRoute.kt

@@ -0,0 +1,31 @@
+import org.http4k.core.Body
+import org.http4k.core.ContentType.Companion.TEXT_HTML
+import org.http4k.core.Method.GET
+import org.http4k.core.Response
+import org.http4k.core.Status.Companion.OK
+import org.http4k.core.with
+import org.http4k.routing.Route
+import org.http4k.routing.by
+import org.http4k.template.HandlebarsTemplates
+import org.http4k.template.ViewModel
+import org.http4k.template.view
+
+data class Fortune(val id: Int, val message: String)
+
+data class FortunesList(val items: List<Fortune>) : ViewModel
+
+object FortunesRoute {
+
+    private val viewBody = Body.view(HandlebarsTemplates().CachingClasspath(), TEXT_HTML)
+
+    operator fun invoke(database: Database): Route = GET to "fortunes" by {
+        val items = database.withConnection {
+            it.prepareStatement("select * from fortune").executeQuery().toList {
+                Fortune(it.getInt(1), it.getString(2))
+            }
+        }
+            .plus(Fortune(0, "Additional fortune added at request time."))
+            .sortedBy { it.message }
+        Response(OK).with(viewBody of FortunesList(items))
+    }
+}

+ 8 - 26
frameworks/Kotlin/http4k/src/main/kotlin/Http4kBenchmarkServer.kt

@@ -1,26 +1,12 @@
-
 import org.apache.commons.lang3.time.FastDateFormat.getInstance
-import org.http4k.asByteBuffer
-import org.http4k.contract.Root
-import org.http4k.contract.Route
-import org.http4k.contract.RouteModule
-import org.http4k.contract.SimpleJson
-import org.http4k.core.ContentType
 import org.http4k.core.Filter
-import org.http4k.core.Method.GET
-import org.http4k.core.Response
-import org.http4k.core.Status.Companion.OK
-import org.http4k.core.with
-import org.http4k.format.Jackson
-import org.http4k.format.Jackson.json
-import org.http4k.lens.Body
+import org.http4k.core.then
+import org.http4k.routing.routes
 import org.http4k.server.ServerConfig
 import org.http4k.server.asServer
 import java.util.TimeZone.getTimeZone
 
 object Http4kBenchmarkServer {
-    private val json = Jackson
-    private val preAllocatedHelloWorldText = "Hello, World!".asByteBuffer()
     private val dateFormat = getInstance("EEE, d MMM yyyy HH:mm:ss 'GMT'", getTimeZone("GMT"))
 
     private val dateAndServer = Filter {
@@ -32,16 +18,12 @@ object Http4kBenchmarkServer {
         }
     }
 
-    private val jsonBody = Body.json().required()
-    private val plainTextBody = Body.binary(ContentType.TEXT_PLAIN).required()
+    private val database = Database(System.getenv("DBHOST") ?: "localhost")
 
-    private val module = RouteModule(Root, SimpleJson(json), dateAndServer)
-        .withRoute(Route("plaintext").at(GET) / "plaintext" bind {
-            Response(OK).with(plainTextBody to preAllocatedHelloWorldText)
-        })
-        .withRoute(Route("json").at(GET) / "json" bind {
-            Response(OK).with(jsonBody to json.obj("message" to json.string("Hello, World!")))
-        })
+    val routes = listOf(PlainTextRoute(),
+        JsonRoute(),
+        FortunesRoute(database)
+    ).plus(WorldRoutes(database))
 
-    fun start(config: ServerConfig) = module.toHttpHandler().asServer(config).start().block()
+    fun start(config: ServerConfig) = dateAndServer.then(routes(*routes.toTypedArray())).asServer(config).start().block()
 }

+ 5 - 0
frameworks/Kotlin/http4k/src/main/kotlin/Http4kUndertowServer.kt

@@ -0,0 +1,5 @@
+import org.http4k.server.Undertow
+
+fun main(args: Array<String>) {
+    Http4kBenchmarkServer.start(Undertow(9000))
+}

+ 15 - 0
frameworks/Kotlin/http4k/src/main/kotlin/JsonRoute.kt

@@ -0,0 +1,15 @@
+import org.http4k.core.Body
+import org.http4k.core.Method.GET
+import org.http4k.core.Response
+import org.http4k.core.Status.Companion.OK
+import org.http4k.core.with
+import org.http4k.format.Jackson.json
+import org.http4k.format.Jackson.obj
+import org.http4k.format.Jackson.string
+import org.http4k.routing.by
+
+object JsonRoute {
+    private val jsonBody = Body.json().toLens()
+
+    operator fun invoke() = GET to "/json" by { Response(OK).with(jsonBody of obj("message" to string("Hello, World!"))) }
+}

+ 18 - 0
frameworks/Kotlin/http4k/src/main/kotlin/PlainTextRoute.kt

@@ -0,0 +1,18 @@
+
+import org.http4k.asByteBuffer
+import org.http4k.core.Body
+import org.http4k.core.ContentType.Companion.TEXT_PLAIN
+import org.http4k.core.Method.GET
+import org.http4k.core.Response
+import org.http4k.core.Status.Companion.OK
+import org.http4k.core.with
+import org.http4k.lens.binary
+import org.http4k.routing.by
+
+object PlainTextRoute {
+    private val preAllocatedHelloWorldText = "Hello, World!".asByteBuffer()
+
+    private val plainTextBody = Body.binary(TEXT_PLAIN).toLens()
+
+    operator fun invoke() = GET to "/plaintext" by { Response(OK).with(plainTextBody of preAllocatedHelloWorldText) }
+}

+ 87 - 0
frameworks/Kotlin/http4k/src/main/kotlin/WorldRoutes.kt

@@ -0,0 +1,87 @@
+
+import com.fasterxml.jackson.databind.JsonNode
+import org.http4k.core.Body
+import org.http4k.core.Method.GET
+import org.http4k.core.Response
+import org.http4k.core.Status.Companion.NOT_FOUND
+import org.http4k.core.Status.Companion.OK
+import org.http4k.core.with
+import org.http4k.format.Jackson.array
+import org.http4k.format.Jackson.json
+import org.http4k.format.Jackson.number
+import org.http4k.format.Jackson.obj
+import org.http4k.lens.Query
+import org.http4k.routing.Route
+import org.http4k.routing.by
+import java.lang.Math.max
+import java.lang.Math.min
+import java.sql.Connection
+import java.sql.ResultSet.CONCUR_READ_ONLY
+import java.sql.ResultSet.TYPE_FORWARD_ONLY
+import java.util.*
+
+
+object WorldRoutes {
+
+    private val jsonBody = Body.json().toLens()
+
+    private val numberOfQueries = Query
+        .map {
+            try {
+                min(max(it.toInt(), 1), 500)
+            } catch (e: Exception) {
+                1
+            }
+        }
+        .defaulted("queries", 1)
+
+    operator fun invoke(database: Database): List<Route> =
+        listOf(
+            queryRoute(database),
+            multipleRoute(database),
+            updateRoute(database)
+        )
+
+    private fun queryRoute(database: Database): Route = GET to "db" by {
+        database.withConnection {
+            findWorld(it, randomWorld())
+        }?.let { Response(OK).with(jsonBody of it) } ?: Response(NOT_FOUND)
+    }
+
+    private fun multipleRoute(database: Database): Route = GET to "queries" by {
+        val worlds = database.withConnection {
+            con ->
+            (1..numberOfQueries(it)).mapNotNull { findWorld(con, randomWorld()) }
+        }
+        Response(OK).with(jsonBody of array(worlds))
+    }
+
+    private fun updateRoute(database: Database): Route = GET to "updates" by {
+        val worlds = database.withConnection {
+            con ->
+            (1..numberOfQueries(it)).mapNotNull {
+                val id = randomWorld()
+                updateWorld(con, id)
+                findWorld(con, id)
+            }
+        }
+        Response(OK).with(jsonBody of array(worlds))
+    }
+
+    private fun findWorld(it: Connection, id: Int): JsonNode? {
+        val stmtSelect = it.prepareStatement("select * from world where id = ?", TYPE_FORWARD_ONLY, CONCUR_READ_ONLY)
+        stmtSelect.setInt(1, id)
+        return stmtSelect.executeQuery().toList {
+            obj("id" to number(it.getInt("id")), "randomNumber" to number(it.getInt("randomNumber")))
+        }.firstOrNull()
+    }
+
+    private fun updateWorld(it: Connection, id: Int) {
+        val stmtSelect = it.prepareStatement("update world set randomNumber = ? where id = ?")
+        stmtSelect.setInt(1, randomWorld())
+        stmtSelect.setInt(2, id)
+        stmtSelect.executeUpdate()
+    }
+
+    private fun randomWorld() = Random().nextInt(9999) + 1
+}

+ 1 - 0
frameworks/Kotlin/http4k/src/main/resources/FortunesList.hbs

@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>{{#items}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/items}}</table></body></html>