https://kotlinlang.org logo
#ktor
Title
# ktor
a

Alexander Städing

03/29/2024, 10:40 AM
Hey guys, I'm struggling with a weird issue in ktor using protobuf content negotiation. It seems that the application/protobuf Content-Type header is being set on the client but not received on the server. When I set the Content-Type header to something else (e.g. text) it works. Any ideas? Here is the code:
Copy code
client.post("<http://localhost:8080/launch>") {
            contentType(ContentType.Application.ProtoBuf)
            setBody(launch)
            println("Sent content type: ${contentType()}")
        }
This prints:
Copy code
Sent content type: application/protobuf
Response: HttpResponse[<http://localhost:8080/launch>, 415 Unsupported Media Type]
But the server receives no Content-Type header:
Copy code
[24-Mar-29 11:37:47] [INFO] - Autoreload is disabled because the development mode is off.
[24-Mar-29 11:37:47] [INFO] - Application started in 0.034 seconds.
[24-Mar-29 11:37:47] [INFO] - Responding at <http://0.0.0.0:8080>
[24-Mar-29 11:37:53] [WARN] - Content-Type: null
Copy code
val logger by inject<Logger>()
        post("/launch") {
            val contentType = call.request.header(HttpHeaders.ContentType)
            logger.warn("Content-Type: $contentType")
If I use,
contentType(ContentType.Text.Plain
, the server prints:
Copy code
[24-Mar-29 11:39:24] [WARN] - Content-Type: text/plain; charset=UTF-8
P.S. I am using
.proto
files which are converted to Java with the protobuf gradle plugin. I wrote my own
ContentConverter
and register it like this:
Copy code
val client = HttpClient {
        install(ContentNegotiation) {
            register(ContentType.Application.ProtoBuf, ProtobufContentConverter())
        }
    }
a

Aleksei Tirman [JB]

03/29/2024, 10:45 AM
Can you please check if the Content-Type header is actually sent through the network?
a

Alexander Städing

03/29/2024, 10:45 AM
How should I check that?
a

Aleksei Tirman [JB]

03/29/2024, 10:46 AM
You can use a network traffic analyzing tool, for example, https://www.wireshark.org/
a

Alexander Städing

03/29/2024, 10:54 AM
It doesn't seem to get sent.
I tried again with plain text Content-Type and it is visible in wireshark
It seems the header is being prevented from being sent when I use the
setBody
method with the protobuf object. If I use
setBody("foo")
the protobuf content type header is sent.
This is the serialization method. The
writeTo
method is called:
Copy code
override suspend fun serializeNullable(contentType: ContentType, charset: Charset, typeInfo: TypeInfo, value: Any?): OutgoingContent? {
        if (value !is GeneratedMessage) return null
        return object : OutgoingContent.WriteChannelContent() {
            override suspend fun writeTo(channel: ByteWriteChannel) = value.writeTo(channel.toOutputStream())
        }
    }
a

Aleksei Tirman [JB]

03/29/2024, 11:31 AM
Can you share the definition of the
ProtobufContentConverter
?
a

Alexander Städing

03/29/2024, 11:33 AM
I have a feeling I implemented
serializeNullable
incorrectly, and that I have to somehow write the ContentType again. Here is the full code: https://gist.github.com/alexstaeding/27b7bc51d8ed4edcb1170d4016e4e50f
However, I can't find any documentation that mentions how to deal with the ContentType in this method
a

Aleksei Tirman [JB]

03/29/2024, 11:34 AM
You need to override the
contentType
property of the
OutgoingContent.WriteChannelContent
object in the
serializeNullable
method.
a

Alexander Städing

03/29/2024, 11:36 AM
Ah yes, that makes sense. Thanks for taking the time to help, it works now 👍