Do we already have a way to disable the `Accept` h...
# ktor
s
Do we already have a way to disable the
Accept
headers from the client-side
ContentNegotiation
plugin? I'm using a JSON api that can return a downloadable file for one endpoint if you pass
Accept: application/octet-stream
instead, but if I set that in my
client.get{}
then ContentNegotiation adds
application/json
afterwards every time, and the API seems to pick the last supplied header. AKA I can never actually download this file unless I disable contentegotiation globally, which is not exactly convenient.
1
u
I had the same issue, the majority of all the calls are json and therefore we use ContentNegotiation plugin with json. However we have a few use cases where we download pdf files. We then set:
Copy code
accept(ContentType.Application.Pdf)
in the builder and stream the response to file with bodyAsChannel()
This is a very hacky _workaround_:
Copy code
// ContentNegotiation plugin unconditionally adds accept headers for all supported conversions.
        // However, if we explicitly set the accept header we don't want any to be added
        client.requestPipeline.intercept(HttpRequestPipeline.Transform) { content ->
            val request = this.context
            val hasMultipleAccept = (request.headers.getAll(HttpHeaders.Accept)?.size ?: 0) > 1
            if (hasMultipleAccept) {
                // Remove all headers added by ContentNegotiation (for us it's only json)
                request.headers.remove(HttpHeaders.Accept, ContentType.Application.Json.toString())
            }
            proceedWith(content)
        }
a
Have you tried registering the converter for the
application/octet-stream
content type?
Copy code
install(ContentNegotiation) {
    register(ContentType.Application.OctetStream, KotlinxSerializationConverter(Json {  })) 
}
s
The downloaded file is binary, not JSON. I don't think there's a converter for that?
u
Also, the ContentNegotiation plugin would still attach all content-types it supports to the accept header
☝️ 1
check ContentNegotiation.kt
Copy code
...
internal suspend fun convertRequest(request: HttpRequestBuilder, body: Any): Any? {
    registrations.forEach {
        LOGGER.trace("Adding Accept=${it.contentTypeToSend.contentType} header for ${request.url}")

        if (request.headers.contains(HttpHeaders.Accept, it.contentTypeToSend.toString())) return@forEach
        request.accept(it.contentTypeToSend)
    }
s
Exactly. I'll give your suggestion above a shot.
It's indeed a bit hacky but it seems to work after adapting it a bit to fit my use case. I'll stick with it until we get an official solution built-in. Thanks. On a sidenote, this seems to use the old plugin API, is there a similar solution for the 2.x plugin API?
u
i think this would be equivalent (untested):
Copy code
createClientPlugin("RemoveContentNegotiationAccept") {
    transformRequestBody { request, _, _ ->
        val hasMultipleAccept = (request.headers.getAll(HttpHeaders.Accept)?.size ?: 0) > 1
        if (hasMultipleAccept) {
            // Remove all headers added by ContentNegotiation (for us it's only json)
            request.headers.remove(HttpHeaders.Accept, ContentType.Application.Json.toString())
        }
        null
    }
}
👍 1