if i send request with body parsed from object via...
# ktor
a
if i send request with body parsed from object via KotlinxSerializer i get incorrect request body (should be json content-type, but really is form-data) what may be reason of this?
d
Do you have a small snippet with your client request code + server processing it?
i try send request to https://requestbin.fullcontact.com/10mqr3p1 and see request data...and it was with form-data - https://requestbin.fullcontact.com/10mqr3p1?inspect
Снимок экрана 2018-08-20 в 16.38.58.png
d
Checking. (Unrelated to that question. You can avoid the cast with this transformation):
Copy code
serializer = KotlinxSerializer()
(serializer as KotlinxSerializer).setWriteMapper(TestRequest::class, requestMapper)
(serializer as KotlinxSerializer).setReadMapper(TestResponse::class, responseMapper)

->

serializer = KotlinxSerializer().apply {
    setWriteMapper(TestRequest::class, requestMapper)
    setReadMapper(TestResponse::class, responseMapper)    
}
That depends entirely on the backend right?
So that backend is not yours? And you only have client code? Maybe they accept JSON, but respond with a urlencoded-form. That’s perfectly valid
a
service https://requestbin.fullcontact.com not our, it's used just for debug. also we check sended request by mitmproxy and in request body formatted as formdata
we start from http://reqres.in/api/login to develop without completed backend. if create request from native on android by retrofit or alamofire on ios - send correct, but by ktor we get incorrect body
d
then maybe that service uses formdata
instead of expecting Json, you can expect formdata instead
a
not, we check via mitmproxy request what sended from client (before it received by server) and it contain form-data
d
maybe I’m not understanding well. If you says that it contains form-data that’s what I said, right? It returns form-data and you should process it as if it is form-data
a
i send request from ktor-client i try set json body, not form-data
d
I’m looking at the image. Maybe that’s what is wrong: From the header is says that it returns
application/x-www-form-urlencoded
But the body is a json (from the image)
which looks wrong
if you have to communicate with a fauly backend, maybe you can try to read the request as a String
and then parse it by yourself as Json
a
wrong that ktor-client not set "Content-Type: application/json" and write in body: "<jsonobject>:"
d
so in the image I’m viewing the “request body”, the “response headers” and the “response body”
do you have the request headers?
a
all on image is request data, not response
d
oh I see, let me recheck
sorry, now I understand, so you are setting the contentType, but it is ignored, right?
are you using 0.9.3 so I can try?
a
we are using ktor_version = '0.9.4-dev-3'
d
okay
a
200818.png
👍 1
d
Can you try this instead?
Copy code
object Json {
    @PublishedApi
    internal val objectMapper = jacksonObjectMapper()

    fun <T> convert(value: Any?, clazz: Class<T>): T = objectMapper.convertValue(value, clazz)
    fun <T> parse(str: String, clazz: Class<T>): T = objectMapper.readValue(str, clazz)
    fun <T> stringify(value: T): String = objectMapper.writeValueAsString(value)
}

<http://client.post|client.post><String> {
    body = TextContent(Json.stringify(...yourobject.,,), ContentType.Application.Json)
    contentType(ContentType.Any)
}
a
ok, we'll try it
What should I implement in build.gradle files to get jacksonObjectMapper?
d
compile "io.ktor:ktor-jackson:$ktor_version"
a
Can we use ktor Jackson in multiplatform app (iOS, android)? Because we have problem when adding it to project - unresolved reference: jacksonObjectMapper.
d
no, you cannot
I didn’t know it was a mpp project
if you have a serializer, use it
the idea is to use TextContent with a json string instead of replying with the object itself
to see if TextContent + ContentType sets the content-type properly
a
i try set
Copy code
contentType(ContentType.Application.Json)
                    accept(ContentType.Application.Json)
                    body = TextContent(reqBody, ContentType.Application.Json)
and now request complete success. so serializer of kotlinxserialization is bugged, yes?
here request info after changes of code
d
probably setting using contentType doesn’t work, and the documentation is wrong, I will update it
a
as i see in code - this line incorrect https://github.com/ktorio/ktor/blob/9832fabd5a6966f44ce40aa6ed4cabe965a303d7/ktor-client/ktor-client-features/ktor-client-json/src/io/ktor/client/features/json/JsonFeature.kt#L55 when using JsonFeature we can't set json content-type because it automatically remove from headers in interceptor. what reason for this action?
in this commit https://github.com/ktorio/ktor/commit/fb89266803d9caba74d492bb1871f913fe63aa94#diff-36d5a6bea434b2c6690511910708f9c2 was removed writing of headers from
TextContent
, and JsonFeature expect what contentType from
TextContent
should be set as header, but it not set. our changed code is work because we set content type by call
contentType(ContentType.Application.Json)
but JsonFeature remove this header in interceptor and not set header after it...
CIOHttpRequest & ApacheRequestProducer was updated to
Copy code
val contentLength = headers[HttpHeaders.ContentLength] ?: content.contentLength?.toString()
val contentType = headers[HttpHeaders.ContentType] ?: content.contentType?.toString()

contentLength?.let { builder.headerLine(HttpHeaders.ContentLength, it)}
contentType?.let { builder.headerLine(HttpHeaders.ContentType, it)}
but in android i see:
Copy code
private suspend fun AndroidHttpRequest.execute(): AndroidHttpResponse {
        val requestTime = GMTDate()

        val url = URLBuilder().takeFrom(url).buildString()
        val contentLength = headers[HttpHeaders.ContentLength]?.toLong() ?: content.contentLength

        val context = Job()

        val connection = (URL(url).openConnection() as HttpURLConnection).apply {
            connectTimeout = config.connectTimeout
            readTimeout = config.socketTimeout

            requestMethod = method.value
            useCaches = false
            instanceFollowRedirects = false

            headers.forEach { key, value ->
                addRequestProperty(key, value.joinToString(";"))
            }

            if (this@execute.content !is OutgoingContent.NoContent) {
                if (contentLength != null) {
                    addRequestProperty(HttpHeaders.ContentLength, contentLength.toString())
                } else {
                    addRequestProperty(HttpHeaders.TransferEncoding, "chunked")
                }

                contentLength?.let { setFixedLengthStreamingMode(it) } ?: setChunkedStreamingMode(0)
                doOutput = true

                    this@execute.content.writeTo(outputStream)
            }
        }

        connection.connect()
        val content = connection.content(dispatcher)
        val headerFields = connection.headerFields

        val responseHeaders = HeadersBuilder().apply {
            headerFields?.forEach { (key, values) -> key?.let { appendAll(it, values) } }
        }.build()

        return AndroidHttpResponse(
            call, content, context,
            responseHeaders, requestTime, GMTDate(),
            HttpStatusCode.fromValue(connection.responseCode), HttpProtocolVersion.HTTP_1_1,
            connection
        )
    }
contentLength was set, but content-type - not.
& i found commit what should fix all https://github.com/ktorio/ktor/commit/cc0dded21f03d5a1e5b36f6f45a8bd42080322a6 it was already released in some version?
when will be new alpha release?
d
We don’t have predictable release dates, so not sure when it will be released. My suggestion is that you try to compile it locally, check if it fixes your issue, and upload a version to your own artifactory/bintray in the meantime if it is the case. This way you can continue your work without our release cycle being a hurdle. You can also create a function/extension method that encapsulates the way it worked for you, and call to it. Then when the fix is available you can change just that function. And if you want to remove it later you can use
@Deprecated("")
and autogenerate the replaceWith with idea, so it gets replaced without you having to do it manually