Can you point me to some good examples and/or clea...
# ktor
a
Can you point me to some good examples and/or clear documentation on how to deserialize parameters when using Ktor's
ContentNegotiation
feature? I've been banging my head against the wall for ours and it is still not working for me. I have parameter classes with the
@Searializable
annotation:
Copy code
@Serializable
data class ShowPostURL(
        val date: String,
        val titleSlug: String,
        override val redirectTo: String? = null
)
and no matter what I do
call.receive()
won't work. I'm getting
HTTP 415
errors and Ktor doesn't even log anything. I've added the serialization support as well:
Copy code
install(ContentNegotiation) {
    json
}
How do I fix this? This is how I'm trying to use it:
Copy code
accept(ContentType.Any) {
    get(operation.route) {
        val input = call.receive(ShowPostURL::class)
        call.respondText("foo")
    }
}
a
I've already been there, it doesn't help
and what you linked says that it is deprecated (the
serialization()
part)
and the docs don't say how to use this with
receive
image.png
I'm supposed to see results for basically anything if I use
ContentType.Any
this class is not received as json though
i'm trying to deserialize form the path like this
Copy code
/foo/{date}/{titleSlug}?redirectTo=bar
e
Copy code
install(ContentNegotiation) {
        json()
    }
a
is this even possible with
receive()
e
Could you try adding
()
after json?
a
i tried, it says it doesn't have an invoke method
but i'm not trying to deserialize form
json
but form path / query parameters
e
Strange, it works for me. Could you share the imports you're using?
a
Copy code
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.request.receive
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.accept
import io.ktor.routing.get
e
import io.ktor.serialization.*
could you add that one?
a
i added it but intellij says it is unused
and i get 415 😞
e
I'm lost. Could you file an issue with snippet? Let's figure out what's happening
a
this is a MWE
Copy code
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.http.ContentType
import io.ktor.request.receive
import io.ktor.response.respondText
import io.ktor.routing.accept
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.serialization.json
import io.ktor.server.engine.applicationEngineEnvironment
import io.ktor.server.engine.connector
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.serialization.Serializable

fun main(args: Array<String>) {
    val env = applicationEngineEnvironment {
        module {
            example()
        }
        connector {
            port = 8080
            host = "0.0.0.0"
        }
    }
    embeddedServer(Netty, env).apply {
        start(wait = true)
    }
}

@Serializable
data class PostURL(
        val date: String,
        val titleSlug: String,
        val redirectTo: String? = null
)

fun Application.example() {

    install(ContentNegotiation) {
        json()
    }

    routing {
        accept(ContentType.Any) {
            get("/foo/{date}/{titleSlug}") {
                val input = call.receive(PostURL::class)
                println("$input was received.")
                call.respondText("foo")
            }
        }
    }
}
run it and call it with
<http://localhost:8080/foo/2018-10-21/wtf-is-this.html>
you'll get 415
@e5l I put this in a
try/catch
and this is what i'm getting:
Copy code
io.ktor.features.UnsupportedMediaTypeException: Content type */* is not supported
	at io.ktor.features.ContentNegotiation$Feature$install$3.invokeSuspend(ContentNegotiation.kt:167)
	at io.ktor.features.ContentNegotiation$Feature$install$3.invoke(ContentNegotiation.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:183)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:110)
	at io.ktor.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:90)
	at org.agorahq.agora.delivery.ExampleKt$example$2$2$1.invokeSuspend(Example.kt:54)
	at org.agorahq.agora.delivery.ExampleKt$example$2$2$1.invoke(Example.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:183)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.routing.Routing.executeResult(Routing.kt:147)
	at io.ktor.routing.Routing.interceptor(Routing.kt:34)
	at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:99)
	at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
	at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:107)
	at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:183)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:120)
	at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:183)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:40)
	at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:111)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
	at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:30)
	at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:24)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:59)
	at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:368)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
it seems that i need to add support for this somehow
but it is not in the docs how to do that
e
It looks like we don't support patterns in matcher
a
is there no way to deserialize an url into a class? 😮
e
You can try using
Location
for that
a
is it stable enough?
the docs say it is an experimental api
apart from that I don't want to pollute my business logic with Ktor annotations
that's one of the primary reasons why i'm not using Spring
e
Yep, but we're not going to break it
a
is there an alternative?
e
Yep, fair point
a
my business modules don't know about Ktor 😞
e
Let me thing a bit
a
thanks 👍
n
I ran into this same problem (including not wanting to risk using the experimental Location API). I ended up writing a short function to deserialize the parameters.
a
what's the difference between
call.parameters
and
call.receiveParameters()
? It is not clear from the docs
n
receiveParameters
gets the request body as a
Parameters
object.
call.parameters
is what comes in in the URL. Not great naming.
👍 2
a
oh so
receiveParameters()
is the alternative for
receive()
if you're not doing ContentNegotiation?
thx
n
In practice, it seems to deserialize form data as sent by HTML forms.
So, yeah.
a
can i use
ContentNegotiation
with forms?
call.parameters
seems to include query parameters as well
n
Yes, it does.
A year ago I couldn't get ContentNegotiation to work with forms.
a
this is a bummer
i'm using forms as a fallback method if javascript doesn't work for some reason 😞
n
I ended up using an extension function on the StringValues class to do both form deserialization and url parameters serialization.
Since
receiveParameters
and
parameters
have the same type, you can deserialize both the same way, once you have a way to do one.
a
makes sense
thanks
👍 1
@e5l do you have plans for fixing this?
e
Yep, I filed an issue about the names. And wrote notes about forms with
ContentNegotations
compatibility
Great thanks for the investigation!
a
thanks, @e5l
i'm gonna write a
receiveAs()
method for the time being on the
Parameters
class
so is there something I can do if I want to enable receiving data structures as json and as form data as well?
what i'm trying ot do is to have regular forms
which are enhanced if javascript is present
to API calls
n
Yeah, I was doing something similar. I check the content-type. If it's application/json, I deserialize with the normal
receive
method, if it's not I deserialize with
receiveParameters
and my extension.
a
but right now i can't use the same receiving logic for both
n
All that is behind an extension function on
PipelineContext<Unit, ApplicationCall>
, so you don't have to have the checks everywhere.
a
what do you mean?
can you post an example?
n
Not the actual code, but close:
Copy code
PipelineContext<Unit, ApplicationCall>.myReceive(type: KClass<T>) {
    if (request.contentType().match(ContentType.Application.Json) {
        receive(type);
    } else {
        receiveParameters().myDeserialize(type);
    }
}
So, if it's JSON, the extension acts as though it's just the
receive
method. Otherwise, it uses the custom deserialization.
It doesn't save any overhead on the checks, but it means you don't have to think about them when actually writing route code.
https://ktor.io/servers/features/content-negotiation.html It looks like you could also write your own ContentConverter that handles it for you, and register it. I'm not sure why I didn't find this last year (maybe the docs on this are new), but this actually looks like a better way if it works.
So, form data may not be natively supported, but once you have a way to deserialize you could use the native
receive
method and just register a new
ContentConverter
.
a
thanks, this is great!