Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kotlin: mount of coroutine routes doesn't work #3228

Closed
zzj37 opened this issue Nov 6, 2023 · 4 comments
Closed

kotlin: mount of coroutine routes doesn't work #3228

zzj37 opened this issue Nov 6, 2023 · 4 comments

Comments

@zzj37
Copy link

zzj37 commented Nov 6, 2023

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 JacksonModule globally before mounting routers by mount() method will accept application/json.
  • Adding a customized decoder globally before mounting routers, either by mount() or by install(), will NOT accept application/json
@jknack
Copy link
Member

jknack commented Nov 6, 2023

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)

@jknack jknack added the question label Nov 6, 2023
@zzj37
Copy link
Author

zzj37 commented Nov 7, 2023

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.

@jknack
Copy link
Member

jknack commented Nov 7, 2023

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

@zzj37
Copy link
Author

zzj37 commented Nov 8, 2023

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.

@jknack jknack modified the milestone: 3.0.7 Jan 4, 2024
@jknack jknack changed the title Router with inner incoder/decoder not accepting content type kotlin: mount of coroutine routes doesn't work Feb 26, 2024
@jknack jknack added this to the 3.0.8 milestone Feb 26, 2024
@jknack jknack closed this as completed in 92ee4e7 Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants