A 1:1 port of GenHTTP to Kotlin/JVM.
The goal is to replicate GenHTTP's public API, module set, and architectural concepts in idiomatic Kotlin - coroutines, NIO channels, and suspend functions where GenHTTP uses async/await and System.IO.Pipelines. The resulting behaviour is intended to be identical: the same routing model, the same webservice reflection framework, the same layouting and content modules.
This project is AI-generated and serves as a proof of concept to explore how a full framework port can be produced with AI assistance and to measure where Kotlin lands on throughput benchmarks relative to the .NET original.
No packages are published to Maven Central at this time. A local build is required (see Building from source below).
GenHTTP is a lightweight .NET web framework with a small footprint and a straightforward API. Its documentation covers the full module set - webservices, controllers, functional handlers, layouting, static content, websockets, and more. The concepts described there map directly to this port; the Kotlin equivalents carry the same names and behave the same way.
The example below demonstrates the core of the webservice module: returning primitives, serializing data classes to JSON, and consuming a raw request body stream. It mirrors the pattern described in the GenHTTP webservices documentation.
import kotlinx.coroutines.runBlocking
import org.codegreen.engine.internal.Host
import org.codegreen.modules.layouting.Layout
import org.codegreen.modules.webservices.addService
import org.codegreen.modules.reflection.Method
import org.codegreen.modules.webservices.ResourceMethod
import java.io.InputStream
// Data classes are serialised to JSON automatically.
data class Item(val id: Int, val name: String, val price: Double)
class CatalogService {
private val store = mapOf(
1 to Item(1, "Notebook", 9.99),
2 to Item(2, "Pen", 1.49),
)
// GET /catalog/count → plain integer in the response body
@ResourceMethod("count")
fun count(): Int = store.size
// GET /catalog/:id → JSON-serialised Item
@ResourceMethod(":id")
fun get(id: Int): Item? = store[id]
// POST /catalog/upload → consumes the request body stream, returns byte count
@ResourceMethod("upload", Method.Post)
fun upload(body: InputStream): Long = body.readBytes().size.toLong()
}
fun main() = runBlocking {
val layout = Layout.create()
.addService<CatalogService>("catalog")
Host.create()
.handler(layout)
.console()
.run()
}Build and run the snippet above (or adapt it inside the :playground module):
./gradlew :playground:runThe server starts on http://localhost:8080. Open the following URLs in a browser:
| URL | Returns |
|---|---|
http://localhost:8080/catalog/count |
2 |
http://localhost:8080/catalog/1 |
{"id":1,"name":"Notebook","price":9.99} |
For the stream endpoint, use curl:
curl -X POST http://localhost:8080/catalog/upload --data-binary @somefile
# returns the number of bytes receivedPress Ctrl-C to stop the server.
Requires JDK 17 or later (the Gradle toolchain targets 21).
./gradlew build # compile all modules and run the engine acceptance tests
./gradlew :glyph11:test # run the hardened HTTP/1.1 parser suite onlyOn a machine without a system JDK, install one via SDKMAN:
sdk install java 21-temThe module layout mirrors GenHTTP's solution structure:
| Directory | Gradle project | Mirrors |
|---|---|---|
API/ |
:api |
GenHTTP.Api - core abstractions and the MemoryView type |
Engine/Shared/ |
:engine:shared |
GenHTTP.Engine.Shared - engine-agnostic request/response/hosting types |
Engine/Internal/ |
:engine:internal |
GenHTTP.Engine.Internal - the HTTP/1.1 engine (NIO + coroutines) |
Glyph11/ |
:glyph11 |
The hardened HTTP/1.1 parser, exposing the buffer as MemoryView |
Testing/ |
:testing |
GenHTTP.Testing - the TestHost acceptance harness |
Playground/ |
:playground |
GenHTTP.Playground - a runnable demo (see below) |
Within each module the public API lives at the package root; implementations are in sub-packages (e.g. org.codegreen.engine.shared.types).
The :playground module is modelled on the HttpArena GenHTTP benchmark entry, rebuilt on the Conversion + Reflection + Webservices + Layouting stack. It runs with no external dependencies (the original's PostgreSQL layer is replaced with an in-memory store).
./gradlew :playground:runSample requests:
# plain-text pipeline
curl http://localhost:8080/pipeline
# sum two query parameters; POST variant adds a value from the body
curl "http://localhost:8080/baseline11?a=20&b=22"
curl -X POST "http://localhost:8080/baseline11?a=20&b=22" -d '8'
# JSON response with optional multiplier
curl "http://localhost:8080/json/3"
curl "http://localhost:8080/json/3?m=2"
# upload: returns byte count of the request body
curl -X POST http://localhost:8080/upload --data-binary @somefile
# range query
curl "http://localhost:8080/async-db?min=10&max=12"
# CRUD endpoints
curl "http://localhost:8080/crud/items?category=electronics&page=1&limit=2"
curl -i "http://localhost:8080/crud/items/5"
curl -X POST http://localhost:8080/crud/items \
-H "Content-Type: application/json" \
-d '{"id":5000,"name":"Widget","category":"tools","price":9,"quantity":3}'
curl -X PUT http://localhost:8080/crud/items/5 \
-H "Content-Type: application/json" \
-d '{"name":"Renamed","price":99,"quantity":7}'To run without Gradle attached:
./gradlew :playground:installDist
./Playground/build/install/playground/bin/playground