Hello! Is it possible to have a custom plugin act ...
# ktor
e
Hello! Is it possible to have a custom plugin act as a “middleware” that can abort the whole pipeline(the request) ? When calling “call.respond” , we see that it still continues to other Phases in the background. (why doesn’t call.respond() call “finish()” ?) There’s no access to the pipeline.finish() method when implementing the custom plugin Hook, so we can’t even call it manually ourselves. Our use-case: After the “validate { }” function, we need to check whether our user has “profile completed” status (by checking his claims on the JWT), and if not - respond with some status. But “validate” block itself only has 2 options (if principal is null -> it returns 401, otherwise it just proceeds, so no option to have a third option to it). And if I implement a custom plugin, even if I do there “call.respond(..)” it still proceeds to other plugins instead of finishing the whole pipeline I elaborated more on it here : https://stackoverflow.com/questions/74243213/how-to-stop-pipeline-in-ktors-plugins-middleware-that-sends-a-response-and-abo
r
Is there any specific issues with not finishing the pipeline?
e
yes, it means you have to add a manual check “call.isCommitted” to every subsequent plugin/hook. Take the “validate { .. }” block for example : if you return null instead of a principal, it responds with 401, but in the background it still continues to perform other plugins/hooks. This can lead to unexpected behavior (like multiple responses) and to unnecessary code (like adding “if (call.isCommitted)” to every hook
r
Yes, it does, but all default plugins, such as routing check if call should be processed. And some of the plugins require pipeline to run. Imagine simplified example of metrics: you add 1 to active request count in the beginning of pipeline and subtract from it in the end. That’s why I’m asking if there any specific issues you encounter that we should fix.
e
Ok, so just making sure I understood your example : 1. You have plugins A and B installed, consecutively. 2. in Plugin A - you send a response (e.g - “*call.respond(409)*“) 3. but you still, intentionally, want to get to Plugin B. 4. in Plugin B - you send a task to some PubSub system for example (e.g - pubSub.send( Job/Task(..) ) Programmatically (not related to Ktor) , is this a good practice though? instead of sending this PubSub task for example * before / while* sending a response? And may I ask why you opted for allowing this architectural choice, contrary to what we have in other frameworks, where “call.sendResponse()” automatically cancells all future middlewares? P.S - regarding your question about the issue I encountered : 1. I have to make sure the engineers don’t “forget” per each custom plugin to add
if !call.isCommitted
, i.e - keep in mind always a very specific framework behavior. 2. access to finish() method is restricted, for example the “on() { call ->.. }” method inside Hook, does not give you option to get to the “finish” method 3. having more explicit/expressive code with “proceed()” and “finish()” for every plugin/hook, could result to more predictable and less error prone code. My opinion at least. Thank you Rustam 🙂
r
My example is a bit different, since it concerns local state of the plugin. The rule here, is that one plugin should not change how another plugin works. If you add two hooks in the plugin, you expect that they are called. And if one is skipped because of some other plugin installed, it is unexpected. And worse, as a plugin developer, there is nothing you can do to fix it. With current approach, your hooks will be called, and it’s up to you if you want to handle it. Ktor is a bit different from other frameworks here, because we introduce concept of phases and plugin can intercept pipeline in multiple places. Regarding your issues 1. Is it always necessary to check? Unless plugin calls
call.respond
processing already handled call is safe. Also, if you really need it, it’s easy to write your own wrappers that will check it. 2.
Hook
has custom generic parameter and you can pass
PipelineContext
as a parameter which has
finish()
/`proceed()` methods 3. In complex cases I may agree with you. But majority of plugins and developers, creating this plugins do not need it. So with current approach we provide simplest behaviour by default, while allowing more complex as well
e
I see now, thank you for making it clearer Rustam, I appreciate it 🙂
👍 1