Selaa lähdekoodia

Implemented remaining tests for the Pronghorn server. (#2983)

* Implemented remaining tests for the Pronghorn server.

* Update Pronghorn version.
Chris Turner 8 vuotta sitten
vanhempi
commit
84015a4729
25 muutettua tiedostoa jossa 508 lisäystä ja 48 poistoa
  1. 1 0
      frameworks/Kotlin/pronghorn/.gitignore
  2. 34 5
      frameworks/Kotlin/pronghorn/README.md
  3. 5 1
      frameworks/Kotlin/pronghorn/benchmark_config.json
  4. 10 5
      frameworks/Kotlin/pronghorn/build.gradle
  5. 1 1
      frameworks/Kotlin/pronghorn/setup.sh
  6. 13 1
      frameworks/Kotlin/pronghorn/source_code
  7. 0 33
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/JsonHandler.kt
  8. 15 2
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/TestServer.kt
  9. 20 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/JsonHandler.kt
  10. 24 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestFortunesHandler.kt
  11. 22 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestMultiHandler.kt
  12. 15 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestSingleHandler.kt
  13. 23 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestUpdatesHandler.kt
  14. 6 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/Fortune.kt
  15. 4 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/World.kt
  16. 36 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/codecs/FortuneCodec.kt
  17. 42 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/codecs/WorldCodec.kt
  18. 16 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/JsonSupport.kt
  19. 11 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/LoggingUtils.kt
  20. 6 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/MongoDBClientAttachmentKey.kt
  21. 155 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/MongoDBHandlerHelpers.kt
  22. 35 0
      frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/TestConfig.kt
  23. 7 0
      frameworks/Kotlin/pronghorn/src/main/resources/benchmark.properties
  24. 1 0
      frameworks/Kotlin/pronghorn/src/main/resources/fortunes.httl
  25. 6 0
      frameworks/Kotlin/pronghorn/src/main/resources/httl.properties

+ 1 - 0
frameworks/Kotlin/pronghorn/.gitignore

@@ -1,2 +1,3 @@
 .gradle
 .gradle
 build/
 build/
+out/

+ 34 - 5
frameworks/Kotlin/pronghorn/README.md

@@ -5,26 +5,55 @@ This is the [Pronghorn Http Server](https://www.pronghorn.tech) portion of the [
 The Pronghorn HTTP Server is a low-level, high performance HTTP server written in [Kotlin](https://kotlinlang.org/).
 The Pronghorn HTTP Server is a low-level, high performance HTTP server written in [Kotlin](https://kotlinlang.org/).
 
 
 ### Version
 ### Version
-v0.1.0 : [https://github.com/pronghorn-tech/server/releases/tag/0.1.0](https://github.com/pronghorn-tech/server/releases/tag/0.1.0)
+v0.1.2 : [https://github.com/pronghorn-tech/server/releases/tag/0.1.2](https://github.com/pronghorn-tech/server/releases/tag/0.1.2)
 
 
 ## Test Types
 ## Test Types
-Currently only the plaintext and json tests are implemented.
 
 
 ### Plaintext
 ### Plaintext
 url : `http://TFB-server:8080/plaintext` 
 url : `http://TFB-server:8080/plaintext` 
 source code : [TestServer.kt](src/main/kotlin/pronghorn/TestServer.kt)
 source code : [TestServer.kt](src/main/kotlin/pronghorn/TestServer.kt)
 
 
-### Json
+### Json Serialization
 url : `http://TFB-server:8080/json`
 url : `http://TFB-server:8080/json`
-source code : [JsonHandler.kt](src/main/kotlin/pronghorn/JsonHandler.kt)
+source code : [JsonHandler.kt](src/main/kotlin/pronghorn/handlers/JsonHandler.kt)
+
+### Single Query
+url : `http://TFB-server:8080/db`
+source code : [MongoDBRequestSingleHandler.kt](src/main/kotlin/pronghorn/handlers/MongoDBRequestSingleHandler.kt)
+
+### Multiple Query
+url : `http://TFB-server:8080/queries`
+source code : [MongoDBRequestMultiHandler.kt](src/main/kotlin/pronghorn/handlers/MongoDBRequestMultiHandler.kt)
+
+### Data Updates
+url : `http://TFB-server:8080/updates`
+source code : [MongoDBRequestUpdatesHandler.kt](src/main/kotlin/pronghorn/handlers/MongoDBRequestUpdatesHandler.kt)
+
+### Fortunes
+url : `http://TFB-server:8080/fortunes`
+source code : [MongoDBRequestFortunesHandler.kt](src/main/kotlin/pronghorn/handlers/MongoDBRequestFortunesHandler.kt)
 
 
 ## Additional Dependencies
 ## Additional Dependencies
+
+### Pronghorn Plugins
 Pronghorn is by default a zero dependency library, but for the purpose of benchmarking these tests utilize three Pronghorn plugins for performance and logging :
 Pronghorn is by default a zero dependency library, but for the purpose of benchmarking these tests utilize three Pronghorn plugins for performance and logging :
 * [JCTools Collections Plugin](https://github.com/pronghorn-tech/plugin-collections-jctools) - performance
 * [JCTools Collections Plugin](https://github.com/pronghorn-tech/plugin-collections-jctools) - performance
 * [OpenHFT Hashing Plugin](https://github.com/pronghorn-tech/plugin-hashing-openhft) - performance
 * [OpenHFT Hashing Plugin](https://github.com/pronghorn-tech/plugin-hashing-openhft) - performance
 * [SLF4J Logging Plugin](https://github.com/pronghorn-tech/plugin-logging-slf4j) - logging
 * [SLF4J Logging Plugin](https://github.com/pronghorn-tech/plugin-logging-slf4j) - logging
 
 
-In addition to the above plugins, the json test utilizes the [jsoniter](http://jsoniter.com/) library for the actual json implementation, as well as the [Javassist](http://jboss-javassist.github.io/javassist/) library for improved performance of jsoniter. 
+Additionally, database driven tests utilize the [Pronghorn MongoDB Driver Stream](https://github.com/pronghorn-tech/mongodb-driver-stream) which implements the MongoDB Driver's Stream interface via the [Pronghorn Coroutine Framework](https://github.com/pronghorn-tech/coroutines).  This utilizes the cooperative nature of concurrency in Pronghorn to enable efficient multiplexing of database communication.
+
+### Third-Party Libraries
+Beyond the Pronghorn plugins above, these tests utilize several third party libraries.
+
+#### JsonIter
+Tests requiring json encoding utilize the [jsoniter](http://jsoniter.com/) library, as well as the [Javassist](http://jboss-javassist.github.io/javassist/) library for improved performance of jsoniter.
+
+#### MongoDB Async Driver 
+Database tests depend on the [async MongoDB driver](https://github.com/mongodb/mongo-java-driver/tree/master/driver-async).
+
+#### HTTL
+The Fortunes test utilizes the [httl library](http://httl.github.io/en/) as the template engine.
 
 
 ## Contact
 ## Contact
 For additional information, help, or corrections concerning Pronghorn or these tests contact info [at] pronghorn.tech 
 For additional information, help, or corrections concerning Pronghorn or these tests contact info [at] pronghorn.tech 

+ 5 - 1
frameworks/Kotlin/pronghorn/benchmark_config.json

@@ -5,10 +5,14 @@
       "setup_file": "setup",
       "setup_file": "setup",
       "json_url": "/json",
       "json_url": "/json",
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
       "port": 8080,
       "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Platform",
       "classification": "Platform",
-      "database": "None",
+      "database": "MongoDB",
       "framework": "None",
       "framework": "None",
       "language": "Kotlin",
       "language": "Kotlin",
       "flavor": "None",
       "flavor": "None",

+ 10 - 5
frameworks/Kotlin/pronghorn/build.gradle

@@ -3,8 +3,8 @@ version '0.1.0'
 
 
 buildscript {
 buildscript {
     ext {
     ext {
-        pronghornVersion = '0.1.0'
-        kotlinVersion = '1.1.4'
+        pronghornVersion = '0.1.3'
+        kotlinVersion = '1.1.51'
     }
     }
 
 
     repositories {
     repositories {
@@ -21,6 +21,8 @@ apply plugin: 'application'
 
 
 mainClassName = "pronghorn.TestServerKt"
 mainClassName = "pronghorn.TestServerKt"
 
 
+applicationDefaultJvmArgs = ['-server', '-XX:+AggressiveOpts']
+
 repositories {
 repositories {
     mavenCentral()
     mavenCentral()
 }
 }
@@ -35,13 +37,16 @@ dependencies {
     compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
     compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
 
 
-    runtime 'ch.qos.logback:logback-classic:1.2.3'
+    compile 'ch.qos.logback:logback-classic:1.2.3'
 
 
-    compile 'com.jsoniter:jsoniter:0.9.15'
-    compile 'org.javassist:javassist:3.21.0-GA'
+    compile 'com.jsoniter:jsoniter:0.9.15' // json encoding library
+    compile 'org.javassist:javassist:3.21.0-GA' // needed for faster json encoding mode
+    compile 'org.mongodb:mongodb-driver-async:3.5.0' // async MongoDB driver
+    compile 'com.github.httl:httl:1.0.11' // simple template engine
 
 
     compile "tech.pronghorn:server:$pronghornVersion"
     compile "tech.pronghorn:server:$pronghornVersion"
     compile "tech.pronghorn:plugin-collections-jctools:$pronghornVersion"
     compile "tech.pronghorn:plugin-collections-jctools:$pronghornVersion"
     compile "tech.pronghorn:plugin-hashing-openhft:$pronghornVersion"
     compile "tech.pronghorn:plugin-hashing-openhft:$pronghornVersion"
     compile "tech.pronghorn:plugin-logging-slf4j:$pronghornVersion"
     compile "tech.pronghorn:plugin-logging-slf4j:$pronghornVersion"
+    compile "tech.pronghorn:mongodb-driver-stream:$pronghornVersion"
 }
 }

+ 1 - 1
frameworks/Kotlin/pronghorn/setup.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
 #!/bin/bash
 
 
-fw_depends java
+fw_depends java mongodb
 
 
 ./gradlew --no-daemon clean run
 ./gradlew --no-daemon clean run

+ 13 - 1
frameworks/Kotlin/pronghorn/source_code

@@ -1,2 +1,14 @@
 ./pronghorn/src/main/kotlin/pronghorn/TestServer.kt
 ./pronghorn/src/main/kotlin/pronghorn/TestServer.kt
-./pronghorn/src/main/kotlin/pronghorn/JsonHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/handlers/JsonHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestFortunesHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestMultiHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestSingleHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestUpdatesHandler.kt
+./pronghorn/src/main/kotlin/pronghorn/types/Fortune.kt
+./pronghorn/src/main/kotlin/pronghorn/types/World.kt
+./pronghorn/src/main/kotlin/pronghorn/types/codecs/FortuneCodec.kt
+./pronghorn/src/main/kotlin/pronghorn/types/codecs/WorldCodec.kt
+./pronghorn/src/main/kotlin/pronghorn/utils/JsonSupport.kt
+./pronghorn/src/main/kotlin/pronghorn/utils/LoggingUtils.kt
+./pronghorn/src/main/kotlin/pronghorn/utils/MongoDBClientAttachmentKey.kt
+./pronghorn/src/main/kotlin/pronghorn/utils/MongoDBHandlerHelpers.kt

+ 0 - 33
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/JsonHandler.kt

@@ -1,33 +0,0 @@
-package pronghorn
-
-import com.jsoniter.output.EncodingMode
-import com.jsoniter.output.JsonStream
-import tech.pronghorn.http.*
-import tech.pronghorn.http.protocol.CommonContentTypes
-import tech.pronghorn.server.handlers.DirectHttpRequestHandler
-import java.io.ByteArrayOutputStream
-
-data class JsonExample(val message: String)
-
-class JsonHandler: DirectHttpRequestHandler() {
-    companion object {
-        init {
-            JsonStream.setMode(EncodingMode.DYNAMIC_MODE)
-        }
-    }
-
-    private val outputStream = ByteArrayOutputStream()
-    private val stream = JsonStream(outputStream, 32)
-
-    fun toJson(any: Any): ByteArray{
-        outputStream.reset()
-        stream.writeVal(any)
-        stream.flush()
-        return outputStream.toByteArray()
-    }
-
-    override fun handleDirect(exchange: HttpExchange): HttpResponse {
-        val example = JsonExample("Hello, World!")
-        return HttpResponses.OK(toJson(example), CommonContentTypes.ApplicationJson)
-    }
-}

+ 15 - 2
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/TestServer.kt

@@ -1,16 +1,29 @@
 package pronghorn
 package pronghorn
 
 
+import ch.qos.logback.classic.Level
+import com.jsoniter.output.EncodingMode
+import com.jsoniter.output.JsonStream
+import pronghorn.handlers.*
+import pronghorn.utils.TestConfig
+import pronghorn.utils.setLibraryLogging
 import tech.pronghorn.http.HttpResponses
 import tech.pronghorn.http.HttpResponses
 import tech.pronghorn.http.protocol.CommonContentTypes
 import tech.pronghorn.http.protocol.CommonContentTypes
 import tech.pronghorn.server.HttpServer
 import tech.pronghorn.server.HttpServer
-import tech.pronghorn.server.handlers.StaticHttpRequestHandler
+import tech.pronghorn.server.requesthandlers.StaticHttpRequestHandler
 
 
 fun main(args: Array<String>) {
 fun main(args: Array<String>) {
+    JsonStream.setMode(EncodingMode.DYNAMIC_MODE) // enable faster Json encoding mode
+    setLibraryLogging(Level.WARN) // minimize logging from chatty libraries
+
     val helloWorldResponse = HttpResponses.OK("Hello, World!", CommonContentTypes.TextPlain)
     val helloWorldResponse = HttpResponses.OK("Hello, World!", CommonContentTypes.TextPlain)
     val helloWorldHandler = StaticHttpRequestHandler(helloWorldResponse)
     val helloWorldHandler = StaticHttpRequestHandler(helloWorldResponse)
 
 
-    val server = HttpServer("0.0.0.0", 8080)
+    val server = HttpServer(TestConfig.listenHost, TestConfig.listenPort)
     server.registerUrlHandler("/plaintext", helloWorldHandler)
     server.registerUrlHandler("/plaintext", helloWorldHandler)
     server.registerUrlHandlerGenerator("/json", { JsonHandler() })
     server.registerUrlHandlerGenerator("/json", { JsonHandler() })
+    server.registerUrlHandlerGenerator("/db", { worker -> MongoDBRequestSingleHandler(worker) })
+    server.registerUrlHandlerGenerator("/queries", { worker -> MongoDBRequestMultiHandler(worker) })
+    server.registerUrlHandlerGenerator("/fortunes", { worker -> MongoDBRequestFortunesHandler(worker) })
+    server.registerUrlHandlerGenerator("/updates", { worker -> MongoDBRequestUpdatesHandler(worker) })
     server.start()
     server.start()
 }
 }

+ 20 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/JsonHandler.kt

@@ -0,0 +1,20 @@
+package pronghorn.handlers
+
+import com.jsoniter.output.JsonStream
+import pronghorn.utils.JsonSupport
+import tech.pronghorn.http.*
+import tech.pronghorn.http.protocol.CommonContentTypes
+import tech.pronghorn.server.requesthandlers.DirectHttpRequestHandler
+import java.io.ByteArrayOutputStream
+
+data class JsonExample(val message: String)
+
+class JsonHandler: DirectHttpRequestHandler(), JsonSupport {
+    override val outputStream = ByteArrayOutputStream()
+    override val stream = JsonStream(outputStream, 32)
+
+    override fun handleDirect(exchange: HttpExchange): HttpResponse {
+        val example = JsonExample("Hello, World!")
+        return HttpResponses.OK(toJson(example), CommonContentTypes.ApplicationJson)
+    }
+}

+ 24 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestFortunesHandler.kt

@@ -0,0 +1,24 @@
+package pronghorn.handlers
+
+import httl.Engine
+import pronghorn.types.Fortune
+import pronghorn.utils.MongoDBHandlerHelpers
+import tech.pronghorn.http.*
+import tech.pronghorn.http.protocol.CommonContentTypes
+import tech.pronghorn.server.HttpServerWorker
+import java.io.ByteArrayOutputStream
+
+class MongoDBRequestFortunesHandler(worker: HttpServerWorker) : MongoDBHandlerHelpers(worker) {
+    private val collection = getFortunesCollection()
+    private val engine = Engine.getEngine()
+    private val template = engine.getTemplate("/fortunes.httl")
+    private val output = ByteArrayOutputStream()
+
+    suspend override fun handle(exchange: HttpExchange): HttpResponse {
+        val fortunes = findFortunes(collection)
+        fortunes.add(Fortune(0, "Additional fortune added at request time."))
+        output.reset()
+        template.render(mapOf("fortunes" to fortunes), output)
+        return HttpResponses.OK(output.toByteArray(), CommonContentTypes.TextHtml, Charsets.UTF_8)
+    }
+}

+ 22 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestMultiHandler.kt

@@ -0,0 +1,22 @@
+package pronghorn.handlers
+
+import pronghorn.types.World
+import pronghorn.utils.JsonSupport
+import pronghorn.utils.MongoDBHandlerHelpers
+import tech.pronghorn.coroutines.awaitable.InternalFuture
+import tech.pronghorn.coroutines.awaitable.await
+import tech.pronghorn.http.*
+import tech.pronghorn.http.protocol.CommonContentTypes
+import tech.pronghorn.server.HttpServerWorker
+
+class MongoDBRequestMultiHandler(worker: HttpServerWorker) : MongoDBHandlerHelpers(worker), JsonSupport {
+    private val collection = getWorldCollection()
+
+    suspend override fun handle(exchange: HttpExchange): HttpResponse {
+        val future = InternalFuture<Array<World>>()
+        val worldCount = getQueryCount(exchange)
+        findWorlds(collection, future.promise(), worldCount)
+        val results = await(future)
+        return HttpResponses.OK(toJson(results), CommonContentTypes.ApplicationJson)
+    }
+}

+ 15 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestSingleHandler.kt

@@ -0,0 +1,15 @@
+package pronghorn.handlers
+
+import pronghorn.utils.MongoDBHandlerHelpers
+import tech.pronghorn.http.*
+import tech.pronghorn.http.protocol.CommonContentTypes
+import tech.pronghorn.server.HttpServerWorker
+
+class MongoDBRequestSingleHandler(worker: HttpServerWorker) : MongoDBHandlerHelpers(worker) {
+    private val collection = getWorldCollection()
+
+    suspend override fun handle(exchange: HttpExchange): HttpResponse {
+        val world = findWorld(collection)
+        return HttpResponses.OK(toJson(world), CommonContentTypes.ApplicationJson)
+    }
+}

+ 23 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/handlers/MongoDBRequestUpdatesHandler.kt

@@ -0,0 +1,23 @@
+package pronghorn.handlers
+
+import pronghorn.types.World
+import pronghorn.utils.MongoDBHandlerHelpers
+import tech.pronghorn.coroutines.awaitable.InternalFuture
+import tech.pronghorn.coroutines.awaitable.await
+import tech.pronghorn.http.*
+import tech.pronghorn.http.protocol.CommonContentTypes
+import tech.pronghorn.server.HttpServerWorker
+
+class MongoDBRequestUpdatesHandler(worker: HttpServerWorker) : MongoDBHandlerHelpers(worker) {
+    private val collection = getWorldCollection()
+
+    suspend override fun handle(exchange: HttpExchange): HttpResponse {
+        val future = InternalFuture<Array<World>>()
+        val worldCount = getQueryCount(exchange)
+        findWorlds(collection, future.promise(), worldCount)
+        val results = await(future)
+        results.forEach { world -> world.randomNumber = random.nextInt(10000) + 1 }
+        writeWorlds(collection, results)
+        return HttpResponses.OK(toJson(results), CommonContentTypes.ApplicationJson)
+    }
+}

+ 6 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/Fortune.kt

@@ -0,0 +1,6 @@
+package pronghorn.types
+
+class Fortune(val id: Int,
+              val message: String): Comparable<Fortune> {
+    override fun compareTo(other: Fortune): Int = message.compareTo(other.message)
+}

+ 4 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/World.kt

@@ -0,0 +1,4 @@
+package pronghorn.types
+
+class World(val id: Int,
+            var randomNumber: Int)

+ 36 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/codecs/FortuneCodec.kt

@@ -0,0 +1,36 @@
+package pronghorn.types.codecs
+
+import org.bson.*
+import org.bson.codecs.*
+import pronghorn.types.Fortune
+
+object FortuneCodec : Codec<Fortune> {
+    private val fortuneClass = Fortune::class.java
+
+    override fun encode(writer: BsonWriter, value: Fortune, encoderContext: EncoderContext) {
+        writer.writeStartDocument()
+        writer.writeInt32(value.id)
+        writer.writeString(value.message)
+        writer.writeEndDocument()
+    }
+
+    override fun getEncoderClass(): Class<Fortune> = fortuneClass
+
+    override fun decode(reader: BsonReader, decoderContext: DecoderContext): Fortune {
+        reader.readStartDocument()
+        reader.readBsonType()
+        reader.skipName()
+        val id = when(reader.currentBsonType) {
+            BsonType.DOUBLE -> reader.readDouble().toInt()
+            BsonType.INT32 -> reader.readInt32()
+            else -> throw Exception("Unexpected ID Type")
+        }
+        reader.readBsonType()
+        reader.skipName()
+        reader.skipValue()
+        val message = reader.readString("message")
+        val fortune = Fortune(id, message)
+        reader.readEndDocument()
+        return fortune
+    }
+}

+ 42 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/types/codecs/WorldCodec.kt

@@ -0,0 +1,42 @@
+package pronghorn.types.codecs
+
+import org.bson.*
+import org.bson.codecs.*
+import pronghorn.types.World
+
+object WorldCodec: Codec<World> {
+    private val worldClass = World::class.java
+
+    override fun encode(writer: BsonWriter, value: World, encoderContext: EncoderContext) {
+        writer.writeStartDocument()
+        writer.writeInt32(value.id)
+        writer.writeInt32(value.randomNumber)
+        writer.writeEndDocument()
+    }
+
+    override fun getEncoderClass(): Class<World> = worldClass
+
+    override fun decode(reader: BsonReader, decoderContext: DecoderContext): World {
+        reader.readStartDocument()
+        reader.readBsonType()
+        reader.skipName()
+        val id = when(reader.currentBsonType) {
+            BsonType.DOUBLE -> reader.readDouble().toInt()
+            BsonType.INT32 -> reader.readInt32()
+            else -> throw Exception("Unexpected ID Type")
+        }
+        reader.readBsonType()
+        reader.skipName()
+        reader.skipValue()
+        reader.readBsonType()
+        reader.skipName()
+        val randomNumber = when(reader.currentBsonType) {
+            BsonType.DOUBLE -> reader.readDouble().toInt()
+            BsonType.INT32 -> reader.readInt32()
+            else -> throw Exception("Unexpected randomNumber Type")
+        }
+        val world = World(id, randomNumber)
+        reader.readEndDocument()
+        return world
+    }
+}

+ 16 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/JsonSupport.kt

@@ -0,0 +1,16 @@
+package pronghorn.utils
+
+import com.jsoniter.output.JsonStream
+import java.io.ByteArrayOutputStream
+
+interface JsonSupport {
+    val outputStream: ByteArrayOutputStream
+    val stream: JsonStream
+
+    fun toJson(any: Any): ByteArray {
+        outputStream.reset()
+        stream.writeVal(any)
+        stream.flush()
+        return outputStream.toByteArray()
+    }
+}

+ 11 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/LoggingUtils.kt

@@ -0,0 +1,11 @@
+package pronghorn.utils
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.LoggerContext
+import org.slf4j.LoggerFactory
+
+fun setLibraryLogging(level: Level) {
+    val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
+    loggerContext.getLogger("org.mongodb.driver").level = level
+    loggerContext.getLogger("httl").level = level
+}

+ 6 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/MongoDBClientAttachmentKey.kt

@@ -0,0 +1,6 @@
+package pronghorn.utils
+
+import com.mongodb.async.client.MongoClient
+import tech.pronghorn.coroutines.core.WorkerAttachmentKey
+
+object MongoDBClientAttachmentKey : WorkerAttachmentKey<MongoClient>

+ 155 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/MongoDBHandlerHelpers.kt

@@ -0,0 +1,155 @@
+package pronghorn.utils
+
+import com.jsoniter.output.JsonStream
+import com.mongodb.ServerAddress
+import com.mongodb.async.client.*
+import com.mongodb.client.model.Filters
+import com.mongodb.client.model.UpdateOneModel
+import com.mongodb.connection.ClusterSettings
+import com.mongodb.connection.ConnectionPoolSettings
+import org.bson.Document
+import org.bson.codecs.configuration.CodecRegistries
+import pronghorn.types.Fortune
+import pronghorn.types.World
+import pronghorn.types.codecs.FortuneCodec
+import pronghorn.types.codecs.WorldCodec
+import tech.pronghorn.coroutines.awaitable.InternalFuture
+import tech.pronghorn.coroutines.awaitable.await
+import tech.pronghorn.http.HttpExchange
+import tech.pronghorn.mongodb.MultiplexMongoDBStreamFactoryFactory
+import tech.pronghorn.server.HttpServerWorker
+import tech.pronghorn.server.requesthandlers.SuspendableHttpRequestHandler
+import java.io.ByteArrayOutputStream
+import java.util.SplittableRandom
+import java.util.TreeSet
+
+private val queriesBytes = "queries".toByteArray(Charsets.US_ASCII)
+
+abstract class MongoDBHandlerHelpers(worker: HttpServerWorker) : SuspendableHttpRequestHandler(), JsonSupport {
+    private val mongoClient = worker.getOrPutAttachment(MongoDBClientAttachmentKey, { createMongoClient(worker) })
+    protected val random = SplittableRandom()
+    override final val outputStream = ByteArrayOutputStream()
+    override final val stream = JsonStream(outputStream, 32)
+
+    protected fun getWorldCollection(): MongoCollection<World> {
+        return mongoClient
+                .getDatabase(TestConfig.dbName)
+                .getCollection(TestConfig.worldCollectionName)
+                .withDocumentClass(World::class.java)
+    }
+
+    protected fun getFortunesCollection(): MongoCollection<Fortune> {
+        return mongoClient
+                .getDatabase(TestConfig.dbName)
+                .getCollection(TestConfig.fortunesCollectionName)
+                .withDocumentClass(Fortune::class.java)
+    }
+
+    private fun createMongoClient(worker: HttpServerWorker): MongoClient {
+        val poolSettings = ConnectionPoolSettings.builder()
+                .minSize(1)
+                .maxSize(Int.MAX_VALUE) // connections are multiplexed via Pronghorn Stream, so max size is irrelevant
+                .maxWaitQueueSize(0)
+                .build()
+
+        val clusterSettings = ClusterSettings.builder()
+                .hosts(listOf(ServerAddress(TestConfig.dbHost, TestConfig.dbPort)))
+                .build()
+
+        val multiplexedFactory = MultiplexMongoDBStreamFactoryFactory(worker)
+
+        val codecRegistry = CodecRegistries.fromRegistries(
+                MongoClients.getDefaultCodecRegistry(),
+                CodecRegistries.fromCodecs(FortuneCodec, WorldCodec)
+        )
+
+        val settings = MongoClientSettings.builder()
+                .clusterSettings(clusterSettings)
+                .connectionPoolSettings(poolSettings)
+                .streamFactoryFactory(multiplexedFactory)
+                .codecRegistry(codecRegistry)
+                .build()
+
+        val client = MongoClients.create(settings)
+        worker.registerShutdownMethod {
+            client.close()
+        }
+        return client
+    }
+
+    protected fun getQueryCount(exchange: HttpExchange): Int {
+        return Math.max(1, Math.min(500, exchange.requestUrl.getQueryParamAsInt(queriesBytes) ?: 1))
+    }
+
+    suspend protected fun findWorld(collection: MongoCollection<World>): World {
+        val future = InternalFuture<World>()
+        val promise = future.promise()
+        val id = random.nextInt(10000) + 1
+        collection.find(Document("_id", id)).first { world, ex ->
+            when {
+                world != null -> promise.complete(world)
+                ex != null -> promise.completeExceptionally(ex)
+                else -> promise.completeExceptionally(Exception("Missing document."))
+            }
+        }
+        return await(future)
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    protected fun findWorlds(collection: MongoCollection<World>,
+                             promise: InternalFuture.InternalPromise<Array<World>>,
+                             count: Int,
+                             results: Array<World?> = arrayOfNulls(count)) {
+        val id = count
+        val searchDocument = Document("_id", id)
+        collection.find(searchDocument).first { world, ex ->
+            when {
+                world != null -> {
+                    results[count - 1] = world
+                    if (count == 1) {
+                        promise.complete(results as Array<World>)
+                    }
+                    else {
+                        findWorlds(collection, promise, count - 1, results)
+                    }
+                }
+                ex != null -> promise.completeExceptionally(ex)
+                else -> promise.completeExceptionally(Exception("Missing document."))
+            }
+        }
+    }
+
+    protected suspend fun findFortunes(collection: MongoCollection<Fortune>): TreeSet<Fortune> {
+        val future = InternalFuture<Unit>()
+        val promise = future.promise()
+        val fortunes = TreeSet<Fortune>()
+
+        collection.find().into(fortunes, { _, _ -> promise.complete(Unit) })
+
+        await(future)
+        return fortunes
+    }
+
+    protected suspend fun writeWorlds(collection: MongoCollection<World>,
+                                      results: Array<World>) {
+        val updateFuture = InternalFuture<Unit>()
+        val updatePromise = updateFuture.promise()
+
+        val updates = results.map { result ->
+            UpdateOneModel<World>(
+                    Filters.eq("_id", result.id),
+                    Document("\$set", Document("randomNumber", result.randomNumber))
+            )
+        }
+
+        collection.bulkWrite(updates, { result, ex ->
+            when {
+                result != null -> updatePromise.complete(Unit)
+                ex != null -> updatePromise.completeExceptionally(ex)
+                else -> updatePromise.completeExceptionally(Exception("Unexpected update failure."))
+            }
+        })
+
+        await(updateFuture)
+    }
+}

+ 35 - 0
frameworks/Kotlin/pronghorn/src/main/kotlin/pronghorn/utils/TestConfig.kt

@@ -0,0 +1,35 @@
+package pronghorn.utils
+
+import tech.pronghorn.util.ignoreException
+import java.util.Properties
+
+object TestConfig {
+    private val properties = parsePropertiesConfig()
+    val listenHost = properties.getProperty("listenHost", "0.0.0.0")
+    val listenPort = getIntProperty("listenPort", 8080)
+    val dbHost = properties.getProperty("dbHost", "TFB-database")
+    val dbPort = getIntProperty("dbPort", 27017)
+    val dbName = properties.getProperty("dbName", "hello_world")
+    val fortunesCollectionName = properties.getProperty("fortunesCollectionName", "fortune")
+    val worldCollectionName = properties.getProperty("worldCollectionName", "world")
+
+    private fun getIntProperty(key: String,
+                               default: Int): Int {
+        val result = properties.getProperty(key)
+        return when (result) {
+            null -> default
+            else -> result.toIntOrNull() ?: default
+        }
+    }
+
+    private fun parsePropertiesConfig(): Properties {
+        val properties = Properties()
+        ignoreException {
+            val stream = javaClass.classLoader.getResource("benchmark.properties")?.openStream()
+            if (stream != null) {
+                properties.load(stream)
+            }
+        }
+        return properties
+    }
+}

+ 7 - 0
frameworks/Kotlin/pronghorn/src/main/resources/benchmark.properties

@@ -0,0 +1,7 @@
+listenHost=0.0.0.0
+listenPort=8080
+dbHost=TFB-database
+dbPort=27017
+dbName=hello_world
+fortunesCollectionName=fortune
+worldCollectionName=world

+ 1 - 0
frameworks/Kotlin/pronghorn/src/main/resources/fortunes.httl

@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#var(Set<Fortune> fortunes)--><!--#foreach(fortune in fortunes)--><tr><td>${fortune.id}</td><td>${fortune.message}</td></tr><!--#end--></table></body></html>

+ 6 - 0
frameworks/Kotlin/pronghorn/src/main/resources/httl.properties

@@ -0,0 +1,6 @@
+import.packages+=pronghorn.types
+input.encoding=UTF-8
+output.encoding=UTF-8
+compile.version=1.8
+reloadable=false
+precompiled=false