Does someone know more about plugins? I am trying ...
# ktor
c
Does someone know more about plugins? I am trying to create a custom plugin that will have access to the parameters of the request, but the plugin is not able to access it. Here is an example
Copy code
fun Application.main() {
    routing {
        get("/house/{id}") {
            val id = call.parameters["id"] // this works
            call.respondText("House ID: $id")
        }
    }

    val plugin = createApplicationPlugin("plugin") {
        onCall { call ->
            val id = call.parameters["id"] // this doesn't work
            <http://call.application.log.info|call.application.log.info>("House ID: $id")
        }
    }
    install(plugin)
}
a
A plugin installed on the application level doesn't have access to the route parameters. To solve your problem, you can define a route-scoped plugin and install it into the routing:
Copy code
val plugin = createRouteScopedPlugin("plugin") {
    onCall { call ->
        val id = call.parameters["id"] // this doesn't work
        <http://call.application.log.info|call.application.log.info>("House ID: $id")
    }
}

embeddedServer(Netty, port = 3333) {
    routing {
        install(plugin)
        get("/house/{id}") {
            val id = call.parameters["id"] // this works
            call.respondText("House ID: $id")
        }
    }
}.start(wait = true)
c
Is this by design? The application plugin does still have access to other details like request headers but not the parameters.
a
Yes, because the parameters belong to the routing and aren't present in a request
👍 1
c
I have another question with the plugin hooks, do hooks work differently with the route scoped plugin and application plugin? I'm looking at the documentation and unless I am understanding it incorrectly,
CallSetup
should trigger before
onCall
but this isn't the case when I add this hook on the route scoped plugin:
Copy code
fun <http://Application.app|Application.app>() {
    val key = AttributeKey<String>("route")
    val routePlugin = createRouteScopedPlugin("route") {
        on(CallSetup) { call ->
            <http://call.application.log.info|call.application.log.info>("route") // triggers 2nd
            call.attributes.put(key, "route")
        }
    }

    val applicationPlugin = createApplicationPlugin("app") {
         onCall { call ->
            val value = call.attributes.getOrNull(key)
            <http://call.application.log.info|call.application.log.info>("app $value") // triggers 1st
        }
    }
    install(applicationPlugin)

    routing {
        install(routePlugin)
        get("/house/{id}") {
            val id = call.parameters["id"] // this works
            call.respondText("House ID: $id")
        }
    }
}
a
That's because of the execution order of the phases of the pipeline, which is the underlying mechanism for the hooks. There is a
ApplicationCallPipeline
which has the
Plugins
and the
Call
phases, which are executed in the respective order. The
onCall
hook is executed during the
Plugins
phase. In the
Call
phase, the whole routing is executed, so the
CallSetup
hook is triggered after the
onCall
.
c
I saw that
CallSetup
is mapped to the
Setup
phase which should occur before the
Plugins
phase, but because we install the plugin to the route, is that why it will always happen during the
Call
phase? In this case, is it ever possible to have a route scoped plugin that would trigger before the application plugin? I wanted to create a plugin that would do the following: 1. Get information from the request parameters and store it in the
call.attributes
2. Access the
call.attributes
in the call logging plugin config and add them to the MDC
Follow up question if this is not possible, with the
CallLogging
plugin, we can add fields to the MDC context of the call, is this possible without the usage of the plugin?
a
I saw that
CallSetup
is mapped to the
Setup
phase which should occur before the
Plugins
phase, but because we install the plugin to the route, is that why it will always happen during the
Call
phase?
Yes, because the entire routing is executed during the
Call
phase and the
Setup
phase is of the routing's call pipeline (routing is a call pipeline).
In this case, is it ever possible to have a route scoped plugin that would trigger before the application plugin?
Only if the application plugin intercepts the phases that go after the
Call
phase
Can you please describe the general problem you're trying to solve?
c
The general problem I want to solve is storing data to the MDC context which comes from the call parameters 1. Create a route plugin that processes the
call.parameters
eg:
/house/3
2. Call an external service to fetch some data using the parameters eg:
service.getNameOfHouseOwner(id)
3. Store the data in the
call.attributes
state eg.
call.attributes.put(name, houseOwnerName)
4. In the call logging plugin (application plugin) I want to access the state that I have stored and put it in the MDC
Copy code
install(CallLogging) { mdc("data") { call.attributes[name] }}
a
The following code works successfully logs the
id
parameter:
Copy code
embeddedServer(Netty, port = 3333) {
    install(CallLogging) {
        mdc("test") { call ->
            call.attributes[key]
        }
    }

    routing {
        get("/test/{id}") {
            val id = call.parameters["id"]
            if (id != null) {
                call.attributes.put(key, id)
            }
            call.respondText { "OK" }
        }
    }
}.start(wait = true)
c
I'm curious why this works when routing should all happen during the
Call
phase while the plugin doesn't... And while this works, it isn't optimal. I wanted to use a plugin to do this functionality for a group of routes that all have the same parameters and populate fields based on it. Without the plugin I would need to repeat the same boilerplate code for each route. I have made some compromises since it doesn't work currently, I created a new request logging plugin with the
onCallRespomd
hook and log our requests combined with our
call.attributes
that we save in our route scoped plugin