Router with inner incoder/decoder not accepting content type
For example:
data class SomeBean (
val name: String
)
class RouterWithDecoder : Kooby({
install(JacksonModule(jacksonObjectMapper()))
get("/api/beans") {
emptyList<SomeBean>()
}
})
fun main(args: Array<String>) {
runApp(args) {
mount(RouterWithDecoder())
}
}
Getting /api/beans with header Accept: application/json will return 406 Not Acceptable with
{
"message": "application/json",
"statusCode": 406,
"reason": "Not Acceptable"
}
If I replace mount(RouterWithDecoder()) with install(::RouterWithDecoder), the server WILL accept application/json.
However, if install(JacksonModule(jacksonObjectMapper())) is replaced by a customized decoder in RouterWithDecoder, eg.
class RouterWithDecoder : Kooby({
// install(JacksonModule(jacksonObjectMapper()))
val jacksonMapper = jacksonObjectMapper()
decoder(MediaType.json) { ctx, type ->
jacksonMapper.readValue(ctx.body.value(), TypeFactory.rawClass(type))
}
get("/api/beans") {
emptyList<SomeBean>()
}
})
then mount the router by install(::RouterWithDecoder) statement will fail to accept application/json again.
After further tests, I find:
- Installing
JacksonModuleglobally before mounting routers bymount()method will acceptapplication/json. - Adding a customized decoder globally before mounting routers, either by
mount()or byinstall(), will NOT acceptapplication/json
Your 2nd second example should work. The way it works is like Parent/Child or MainRoute/SubRoute. So we can define all the routes/API/etc in children while main assemble everything and provide/setup all other services (like jackson here)
Maybe the hierarchical structure need some specifications of some sort of "context" or "scoping"? I find each SubRoute implemented by inheriting Kooby class and mounted by mount() method needs to set its own worker otherwise its coroutine routes do not work.
yea, doc isn't never good enough.
mount: https://jooby.io/#router-composing-mount
The mount operator only import routes. Services, callbacks, etc… are not imported. Main application is responsible for assembly all the resources and services required by imported applications.
install: https://jooby.io/#router-composing-install
This operator lets you for example to deploy Foo as a standalone application or integrate it into a main one called App. The install operator shares the state of the main application, so lazy initialization (and therefore instantiation) of any child applications is mandatory.
I find each SubRoute implemented by inheriting Kooby class and mounted by mount() method needs to set its own worker otherwise its coroutine routes do not work.
This sound like a bug. Can you file it and provide an example?
Thanks
It only happens when calling coroutine within the SubRouter.
class RouterWithoutWorker : Kooby({
coroutine {
get("/without-worker") { "Without worker!" }
}
})
class RouterWithoutWorkerNoCoroutine : Kooby({
get("/without-worker-no-coroutine") { "Without worker, no coroutine!" }
})
class RouterWithWorker(worker: Executor) : Kooby({
this.worker = worker
coroutine {
get("/with-worker") { "With worker!" }
}
})
fun main(args: Array<String>) {
runApp(args) {
mount(RouterWithWorker(this.worker))
mount(RouterWithoutWorker())
coroutine {
mount(RouterWithoutWorkerNoCoroutine())
}
}
}
Both RouterWithWorker and RouterWithoutWorkerNoCoroutine will work, but RouterWithoutWorker will fail with:
java.lang.IllegalStateException: Worker executor not ready
at io.jooby.internal.ForwardingExecutor.execute(ForwardingExecutor.java:18)
at kotlinx.coroutines.ExecutorCoroutineDispatcherImpl.dispatch(Executors.kt:128)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:322)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
at io.jooby.kt.CoroutineRouter.launch$jooby_kotlin(CoroutineRouter.kt:100)
...
I am not sure if calling coroutine within a SubRoute which would later bemount()ed is in accordance to the design goal.