Skip to content

Commit

Permalink
GH-40 Replace reactive plugin with a new routing API
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikoysk committed Nov 10, 2023
1 parent 4d0e808 commit 23883be
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package io.javalin.community.routing

import io.javalin.Javalin
import io.javalin.http.Handler
import io.javalin.http.HandlerType
import io.javalin.router.InternalRouter
import io.javalin.security.RouteRole

class JavalinRoutingExtensions(private val javalin: Javalin) {
Expand All @@ -20,7 +22,7 @@ class JavalinRoutingExtensions(private val javalin: Javalin) {
fun register(): Javalin {
routes
.sortRoutes()
.forEach { javalin.registerRoute(it) }
.forEach { javalin.unsafeConfig().pvt.internalRouter.registerRoute(it) }

return javalin
}
Expand All @@ -34,19 +36,19 @@ data class HandlerEntry @JvmOverloads constructor(
val roles: List<RouteRole> = emptyList(),
) : Routed

fun Javalin.registerRoute(handlerEntry: HandlerEntry) =
fun InternalRouter.registerRoute(handlerEntry: HandlerEntry) =
registerRoute(handlerEntry.route, handlerEntry.path, handlerEntry.handler, *handlerEntry.roles.toTypedArray())

fun Javalin.registerRoute(route: Route, path: String, handler: Handler, vararg roles: RouteRole) {
fun InternalRouter.registerRoute(route: Route, path: String, handler: Handler, vararg roles: RouteRole) {
when (route) {
Route.HEAD -> head(path, handler, *roles)
Route.PATCH -> patch(path, handler, *roles)
Route.OPTIONS -> options(path, handler, *roles)
Route.GET -> get(path, handler, *roles)
Route.PUT -> put(path, handler, *roles)
Route.POST -> post(path, handler, *roles)
Route.DELETE -> delete(path, handler, *roles)
Route.AFTER -> after(path, handler)
Route.BEFORE -> before(path, handler)
Route.HEAD -> addHttpHandler(HandlerType.HEAD, path, handler, *roles)
Route.PATCH -> addHttpHandler(HandlerType.PATCH, path, handler, *roles)
Route.OPTIONS -> addHttpHandler(HandlerType.OPTIONS, path, handler, *roles)
Route.GET -> addHttpHandler(HandlerType.GET, path, handler, *roles)
Route.PUT -> addHttpHandler(HandlerType.PUT, path, handler, *roles)
Route.POST -> addHttpHandler(HandlerType.POST, path, handler, *roles)
Route.DELETE -> addHttpHandler(HandlerType.DELETE, path, handler, *roles)
Route.AFTER -> addHttpHandler(HandlerType.AFTER, path, handler)
Route.BEFORE -> addHttpHandler(HandlerType.BEFORE, path, handler)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.javalin.community.routing.Route.GET
import io.javalin.community.routing.Route.PUT
import io.javalin.http.Handler
import io.javalin.http.HandlerType
import io.javalin.util.Util.firstOrNull
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

Expand All @@ -13,7 +14,7 @@ class JavalinRoutesTest {
@Test
fun `should properly register handler by given enum`() {
// given: a list of routes
val routes = Route.values()
val routes = Route.entries
.map { it to Handler { ctx -> ctx.result(it.name) } }

// when: routes are registered
Expand All @@ -23,9 +24,8 @@ class JavalinRoutesTest {
// then: all routes are registered by as proper HandlerType
routes.forEach { (method, handler) ->
assertThat(
app.javalinServlet()
.matcher
.findEntries(HandlerType.findByName(method.name), "/")
app.unsafeConfig().pvt.internalRouter
.findHttpHandlerEntries(HandlerType.findByName(method.name), "/")
.firstOrNull()
?.handler
).isEqualTo(handler)
Expand All @@ -41,9 +41,8 @@ class JavalinRoutesTest {

listOf("GET", "PUT").forEach { method ->
assertThat(
app.javalinServlet()
.matcher
.findEntries(HandlerType.findByName(method), "/")
app.unsafeConfig().pvt.internalRouter
.findHttpHandlerEntries(HandlerType.findByName(method), "/")
.firstOrNull()
).isNotNull
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
package io.javalin.community.routing.coroutines

import io.javalin.Javalin
import io.javalin.community.routing.Route
import io.javalin.community.routing.coroutines.servlet.CoroutinesServlet
import io.javalin.community.routing.registerRoute
import io.javalin.community.routing.sortRoutes
import io.javalin.config.JavalinConfig
import io.javalin.http.Handler
import io.javalin.plugin.Plugin
import io.javalin.http.HandlerType
import io.javalin.router.InternalRouter
import io.javalin.router.RoutingApiInitializer
import java.util.function.Consumer

class ReactiveRoutingPlugin<ROUTE : ReactiveRoute<CONTEXT, RESPONSE>, CONTEXT, RESPONSE : Any>(
private val servlet: CoroutinesServlet<CONTEXT, RESPONSE>
) : Plugin {
class CoroutinesRouting<CONTEXT, RESPONSE : Any> {
internal val routes = mutableListOf<ReactiveRoute<CONTEXT, RESPONSE>>()

private val routes = mutableListOf<ROUTE>()
fun route(route: ReactiveRoute<CONTEXT, RESPONSE>) {
routes.add(route)
}

override fun apply(app: Javalin) {
routes
.sortRoutes()
.map { it to Handler { ctx -> servlet.handle(ctx, it) } }
.forEach { (reactiveRoute, handler) -> app.registerRoute(reactiveRoute.method, reactiveRoute.path, handler) }
fun route(method: Route, path: String, async: Boolean = true, handler: suspend CONTEXT.() -> RESPONSE) {
routes.add(ReactiveRoute(path, method, async, handler))
}

fun <ROUTES : ReactiveRoutes<ROUTE, CONTEXT, RESPONSE>> routing(vararg reactiveRoutes: ROUTES) {
reactiveRoutes.forEach {
routes.addAll(it.routes())
}
fun routes(exampleEndpoint: ReactiveRoutes<ReactiveRoute<CONTEXT, RESPONSE>, CONTEXT, RESPONSE>): CoroutinesRouting<CONTEXT, RESPONSE> {
exampleEndpoint.routes().forEach { route(it) }
return this
}

}

fun <ROUTE : ReactiveRoute<CONTEXT, RESPONSE>, ROUTES : ReactiveRoutes<ROUTE, CONTEXT, RESPONSE>, CONTEXT, RESPONSE : Any> JavalinConfig.reactiveRouting(
servlet: CoroutinesServlet<CONTEXT, RESPONSE>,
vararg routes: ROUTES
) {
val reactiveRoutingPlugin = ReactiveRoutingPlugin<ROUTE, CONTEXT, RESPONSE>(servlet)
reactiveRoutingPlugin.routing(*routes)
this.plugins.register(reactiveRoutingPlugin)
class Coroutines<ROUTE : ReactiveRoute<CONTEXT, RESPONSE>, CONTEXT, RESPONSE : Any>(
private val servlet: CoroutinesServlet<CONTEXT, RESPONSE>,
) : RoutingApiInitializer<CoroutinesRouting<CONTEXT, RESPONSE>> {

override fun initialize(cfg: JavalinConfig, internalRouter: InternalRouter, setup: Consumer<CoroutinesRouting<CONTEXT, RESPONSE>>) {
val coroutinesRouting = CoroutinesRouting<CONTEXT, RESPONSE>()
setup.accept(coroutinesRouting)

coroutinesRouting
.routes
.sortRoutes()
.map { it to Handler { ctx -> servlet.handle(ctx, it) } }
.forEach { (route, handler) -> internalRouter.addHttpHandler(HandlerType.valueOf(route.method.toString()), route.path, handler) }
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package io.javalin.community.routing

import io.javalin.Javalin
import io.javalin.community.routing.coroutines.Coroutines
import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet
import io.javalin.community.routing.coroutines.ReactiveRoute
import io.javalin.community.routing.coroutines.ReactiveRoutes
import io.javalin.community.routing.coroutines.reactiveRouting
import io.javalin.http.Context
import io.javalin.testtools.JavalinTest
import java.util.concurrent.Executors
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors

class CoroutinesRoutingTest {

Expand All @@ -26,22 +23,19 @@ class CoroutinesRoutingTest {
assertThat(executor.isShutdown).isTrue
}

private val coroutines = Coroutines(DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it })

@Test
fun `should properly execute coroutine`() =
JavalinTest.test(
Javalin.create { config ->
config.reactiveRouting(
DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it },
object : ReactiveRoutes<ReactiveRoute<Context, Unit>, Context, Unit>() {
override fun routes() = setOf(
reactiveRoute("/test", Route.GET) {
withContext(Dispatchers.IO) {
result(Thread.currentThread().name)
}
}
)
config.router.mount(coroutines) {
it.route(Route.GET, "/test") {
withContext(Dispatchers.IO) {
result(Thread.currentThread().name)
}
}
)
}
}
) { _, client ->
assertThat(client.get("/test").body?.string()).contains("DefaultDispatcher")
Expand All @@ -53,18 +47,13 @@ class CoroutinesRoutingTest {
fun `javalin should be able to handle exceptions from coroutines`() =
JavalinTest.test(
Javalin.create { config ->
config.reactiveRouting(
DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it },
object : ReactiveRoutes<ReactiveRoute<Context, Unit>, Context, Unit>() {
override fun routes() = setOf(
reactiveRoute("/test", Route.GET) {
withContext(Dispatchers.IO) {
throw TestException()
}
}
)
config.router.mount(coroutines) {
it.route(Route.GET, "/test") {
withContext(Dispatchers.IO) {
throw TestException()
}
}
)
}
}.exception(TestException::class.java) { _, ctx ->
ctx.result("Handled")
}
Expand All @@ -76,16 +65,11 @@ class CoroutinesRoutingTest {
fun `should execute non-async handlers in regular thread pool`() =
JavalinTest.test(
Javalin.create { config ->
config.reactiveRouting(
DefaultContextCoroutinesServlet(Executors.newSingleThreadExecutor()) { it },
object : ReactiveRoutes<ReactiveRoute<Context, Unit>, Context, Unit>() {
override fun routes() = setOf(
reactiveRoute("/test", Route.GET, async = false) {
result(Thread.currentThread().name)
}
)
config.router.mount(coroutines) {
it.route(Route.GET, "/test", async = false) {
result(Thread.currentThread().name)
}
)
}
}
) { _, client ->
assertThat(client.get("/test").body?.string()).contains("JettyServerThreadPool")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package io.javalin.community.routing.examples

import io.javalin.community.routing.coroutines.ReactiveRoutes
import io.javalin.community.routing.Route.GET
import io.javalin.Javalin
import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet
import io.javalin.community.routing.Route.GET
import io.javalin.community.routing.coroutines.Coroutines
import io.javalin.community.routing.coroutines.ReactiveRoute
import io.javalin.community.routing.coroutines.reactiveRouting
import io.javalin.community.routing.coroutines.ReactiveRoutes
import io.javalin.community.routing.coroutines.servlet.DefaultContextCoroutinesServlet
import io.javalin.http.Context
import kotlinx.coroutines.delay
import java.lang.Thread.sleep
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.delay

// Some dependencies
class ExampleService {
Expand Down Expand Up @@ -78,7 +78,9 @@ fun main() {
// setup Javalin with reactive routing
Javalin
.create { config ->
config.reactiveRouting(coroutinesServlet, ExampleEndpoint(exampleService))
config.router.mount(Coroutines(coroutinesServlet)) {
it.routes(ExampleEndpoint(exampleService))
}
}
.events {
it.serverStopping { coroutinesServlet.prepareShutdown() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package io.javalin.community.routing.dsl

import io.javalin.community.routing.Route
import io.javalin.community.routing.Route.AFTER
import io.javalin.community.routing.Route.BEFORE
import io.javalin.community.routing.Route.DELETE
import io.javalin.community.routing.Route.GET
import io.javalin.community.routing.Route.HEAD
import io.javalin.community.routing.Route.OPTIONS
import io.javalin.community.routing.Route.PATCH
import io.javalin.community.routing.Route.POST
import io.javalin.community.routing.Route.PUT
import io.javalin.community.routing.Routes
import io.javalin.http.ExceptionHandler
import io.javalin.http.Handler
Expand Down Expand Up @@ -28,42 +37,23 @@ open class RoutingDslConfiguration<ROUTE : DslRoute<CONTEXT, RESPONSE>, CONTEXT,
internal val routes = mutableSetOf<ROUTE>()
internal val exceptionHandlers = mutableMapOf<KClass<out Exception>, DslExceptionHandler<CONTEXT, Exception, RESPONSE>>()

fun get(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.GET, path, handler)

fun post(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.POST, path, handler)

fun put(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.PUT, path, handler)

fun delete(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.DELETE, path, handler)

fun patch(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.PATCH, path, handler)

fun head(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.HEAD, path, handler)

fun options(path: String, handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.OPTIONS, path, handler)

fun before(path: String = "", handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.BEFORE, path, handler)

fun after(path: String = "", handler: CONTEXT.() -> RESPONSE) =
addRoute(Route.AFTER, path, handler)

@Suppress("UNCHECKED_CAST")
fun addRoutes(routesToAdd: Collection<DslRoute<CONTEXT, RESPONSE>>) {
routesToAdd.forEach {
routes.add(it as ROUTE)
}
fun get(path: String, handler: CONTEXT.() -> RESPONSE) = route(GET, path, handler)
fun post(path: String, handler: CONTEXT.() -> RESPONSE) = route(POST, path, handler)
fun put(path: String, handler: CONTEXT.() -> RESPONSE) = route(PUT, path, handler)
fun delete(path: String, handler: CONTEXT.() -> RESPONSE) = route(DELETE, path, handler)
fun patch(path: String, handler: CONTEXT.() -> RESPONSE) = route(PATCH, path, handler)
fun head(path: String, handler: CONTEXT.() -> RESPONSE) = route(HEAD, path, handler)
fun options(path: String, handler: CONTEXT.() -> RESPONSE) = route(OPTIONS, path, handler)
fun before(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(BEFORE, path, handler)
fun after(path: String = "", handler: CONTEXT.() -> RESPONSE) = route(AFTER, path, handler)

fun routes(container: DslContainer<ROUTE, CONTEXT, RESPONSE>) = routes(container.routes())
fun routes(routesToAdd: Collection<DslRoute<CONTEXT, RESPONSE>>) = routesToAdd.forEach {
@Suppress("UNCHECKED_CAST")
routes.add(it as ROUTE)
}

fun addRoute(method: Route, path: String, handler: CONTEXT.() -> RESPONSE) {
addRoutes(
fun route(method: Route, path: String, handler: CONTEXT.() -> RESPONSE) {
routes(
listOf(
DefaultDslRoute(
method = method,
Expand All @@ -79,7 +69,6 @@ open class RoutingDslConfiguration<ROUTE : DslRoute<CONTEXT, RESPONSE>, CONTEXT,
if (exceptionHandlers.containsKey(type)) {
throw IllegalArgumentException("Exception handler for type ${type.simpleName} is already registered")
}

exceptionHandlers[type] = handler as DslExceptionHandler<CONTEXT, Throwable, RESPONSE>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ open class DslRouting<
internalRouter.addHttpExceptionHandler(exceptionClass.java, factory.createExceptionHandler(handler))
}
}

}
Loading

0 comments on commit 23883be

Please sign in to comment.