Various experimental proposals and extensions to Javalin 4.x used in Reposilite 3.x

Javalin RFCs

Various experimental extensions to Javalin 4.x used in Reposilite 3.x. Provides basic support for Kotlin coroutines and async routes with a set of useful utilities.

repositories {
    maven { url 'https://repo.panda-lang.org/releases' }
}

dependencies {
    val version = "1.0.8"
    implementation "com.reposilite.javalin-rfcs:javalin-context:$version"
    implementation "com.reposilite.javalin-rfcs:javalin-reactive-routing:$version"
}

Project also includes panda-lang :: expressible library as a dependency. It’s mainly used to provide Result<VALUE, ERROR> type and associated utilities.

Reactive Routing

Experimental router plugin that supports generic route registration with custom context and multiple routes within the same endpoints.

<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="// Custom context
class AppContext(val context: Context)

// Some dependencies
class ExampleFacade

// Endpoint (domain router)
class ExampleEndpoint(private val exampleFacade: ExampleFacade) : AbstractRoutes() {

private val sync = route("/sync", GET, async = false) { blockingDelay("Sync") }

private val blockingAsync = route("/async-blocking", GET) { blockingDelay("Blocking Async") }

private val nonBlockingAsync = route("/async", GET) { nonBlockingDelay("Non-blocking Async") }

override val routes = setOf(sync, blockingAsync, nonBlockingAsync)

}

private suspend fun nonBlockingDelay(message: String): String = delay(100L).let { message }
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun blockingDelay(message: String): String = sleep(100L).let { message }

fun main() {
val exampleFacade = ExampleFacade()
val exampleEndpoint = ExampleEndpoint(exampleFacade)

val sharedThreadPool = QueuedThreadPool(4)
val dispatcher = DispatcherWithShutdown(sharedThreadPool.asCoroutineDispatcher())
sharedThreadPool.start()

Javalin
.create { config ->
config.server { Server(sharedThreadPool) }

ReactiveRoutingPlugin(
errorConsumer = { name, throwable -> println("$name: ${throwable.message}") },
dispatcher = dispatcher,
syncHandler = { ctx, route -> route.handler(AppContext(ctx)) },
asyncHandler = { ctx, route, _ -> route.handler(AppContext(ctx)) }
)
.registerRoutes(exampleEndpoint)
.let { config.registerPlugin(it) }
}
.events {
it.serverStopping { dispatcher.prepareShutdown() }
it.serverStopped { dispatcher.completeShutdown() }
}
.start("127.0.0.1", 8080)
}
“>

// Custom context
class AppContext(val context: Context)

// Some dependencies
class ExampleFacade

// Endpoint (domain router)
class ExampleEndpoint(private val exampleFacade: ExampleFacade) : AbstractRoutes<AppContext, Unit>() {

    private val sync = route("/sync", GET, async = false) { blockingDelay("Sync") }

    private val blockingAsync = route("/async-blocking", GET) { blockingDelay("Blocking Async") }

    private val nonBlockingAsync = route("/async", GET) { nonBlockingDelay("Non-blocking Async") }

    override val routes = setOf(sync, blockingAsync, nonBlockingAsync)

}

private suspend fun nonBlockingDelay(message: String): String = delay(100L).let { message }
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun blockingDelay(message: String): String =  sleep(100L).let { message }

fun main() {
    val exampleFacade = ExampleFacade()
    val exampleEndpoint = ExampleEndpoint(exampleFacade)

    val sharedThreadPool = QueuedThreadPool(4)
    val dispatcher = DispatcherWithShutdown(sharedThreadPool.asCoroutineDispatcher())
    sharedThreadPool.start()

    Javalin
        .create { config ->
            config.server { Server(sharedThreadPool) }

            ReactiveRoutingPlugin<AppContext, Unit>(
                errorConsumer = { name, throwable -> println("$name: ${throwable.message}") },
                dispatcher = dispatcher,
                syncHandler = { ctx, route -> route.handler(AppContext(ctx)) },
                asyncHandler = { ctx, route, _ -> route.handler(AppContext(ctx)) }
            )
            .registerRoutes(exampleEndpoint)
            .let { config.registerPlugin(it) }
        }
        .events {
            it.serverStopping { dispatcher.prepareShutdown() }
            it.serverStopped { dispatcher.completeShutdown() }
        }
        .start("127.0.0.1", 8080)
}

~ source: RoutingExample.kt

Context

Provides utility methods in io.javalin.http.Context class:

Context.error(ErrorResponse)
Context.contentLength(Long)
Context.encoding(Charset)
Context.encoding(String)
Context.contentDisposition(String)
Context.resultAttachment(Name, ContentType, ContentLength, InputStream)

Provides generic ErrorResponse that supports removal of exception based error handling within app:

Result
// […]
“>

ErrorResponse(Int httpCode, String message)
ErrorResponse(HttpCode httpCode, String message)
/* Methods */
errorResponse(HttpCode httpCode, String message) -> Result<*, ErrorResponse>
// [...]

OpenAPI

Reimplemented OpenAPI module:

To enable annotation processor, Swagger or ReDoc you have to add extra dependencies from repository listed above.

Used by

GitHub

https://github.com/reposilite/playground-javalin-rfcs