Please provide a way to bind objects to the request and acces them from anywhere
See original GitHub issueIn Ktor, accessing the incoming call is easy: just use the call
field. However, this field is not global, it is provided by the REST API builder.
Assume you are building a microservice with Ktor. You have your REST endpoints, a service layer, and some repositories - a typical 3-level enterprise stack. You do authentication via JWT. All of that works nicely with Ktor.
Now we would like to call another microservice from one of our services. To do so, we require the JWT token which was passed to the call. We’re in the middle of a service-layer method, probably 5 or 6 function calls deep into the JVM call stack. The call
object (which contains the token in its header) is way out of reach. Passing it down as an argument through all the functions in the service layer would be a major pain as well.
Coming from the Spring framework, for a case like this, you have the SecurityContext
. You would call the static method SecurityContextHolder.getContext()
and get the exact instance of the context which is associated with your current call. This is accomplished via a ThreadLocal<SecurityContext>
that is hidden inside the SecurityContextHolder
. When a call is received, Spring generates the security context, and clears it when call processing is done.
Other prominent use cases for request-attached data are:
- Sessions maintained by O/R-mappers (e.g. JPA)
- Database transactions
- Access control decisions
As far as I know, Ktor at the moment provides no such facilities. Mimicking the spring approach directly is also not feasible, as coroutines and ThreadLocal
in general do not play nice with each other.
I’ve found this article which outlines an approach for storing such data in the CoroutineContext
. However, this has some issues as well:
- Coroutine contexts are, to my knowledge, not nested by default. So whenever I start a sub-coroutine (e.g. via
async
), the context of the parent coroutine (the one which invokedasync()
) is not visible to the child (the one which was produced byasync()
). This is in general very unfortunate, because the programmer A) needs to be aware that the context contains data which might be needed downstream and B) needs to pass the data along explicitly in the coroutine generator function. - The coroutine context API is very convoluted. I just tried to follow the article, the developer experience is honestly quite bad (in essence it’s just a hash map, with type safety factored in). It feels like I have to write a lot of code to accomplish something that is simple, and that is also an often-needed feature. The upshot is: ideally I shouldn’t have to touch it as a Ktor user.
The Ktor-side API which I would imagine is something like this:
object CallContext {
suspend fun get(key: String): Any { ... }
suspend fun set(key: String, value: Any): Any { ... }
}
… and ideally the programmer should not have to explicitly pass along coroutine contexts. The functions are suspend
ing to force the caller to use them from within a coroutine (this allows the implementation to make use of the coroutineContext
). I’m not super experienced with coroutines (yet), so maybe there is a way to do it like this even today. Maybe Ktor already has something like this?
Please feel free to correct me if I took a wrong turn somewhere. I’m still quite new to all of this.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:4
- Comments:7 (2 by maintainers)
Top GitHub Comments
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Actually, there is the context. But the problem is that it is only available in suspend functions. So it should be coroutine context + thread locals with
ThreadLocalElement
support that could do the trick.We are at vacation here so I don’t have a dev environment so can’t verify. But the IDEA is like the following (JVM only):