Ktor Version and Engine Used (client or server and name)
Ktor 1.3.2, Kotlin 1.3.70, Netty server, ContentNegotiation, Jackson parser, Exposed.
I simply used the Ktor project generator for Gradle, and checked a few boxes for modules as mentioned above. After which I managed to connect a database & a few endpoints, but my core endpoint for uploading images doesn't work.
Describe the bug
When trying to upload a simple image using Retrofit & Multipart support in Android, I get a MalformedInputException on my server.
Retrofit/Android code:
@Multipart
@POST("/api/upload")
suspend fun uploadImages(@Part("image") imageFile: RequestBody): UploadResponse
Upload code:
val requestBody: RequestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Image Upload")
.addFormDataPart(
"image", // part name
file.path.split("/").last(), // file name
file.asRequestBody("image/jpg".toMediaType()) // Content-Type: image/jpg
)
.build()
return apiService.uploadImages(requestBody)
Using the new way of creating RequestBody objects in OkHttp. I followed the documentation to receive the multipart data, and I've formed a MalformedInputException.
To Reproduce
Steps to reproduce the behavior:
receiveMultipart() to receive MP on the Ktor side.Expected behavior
Multipart data should be received correctly.
Stack trace:
[nioEventLoopGroup-4-1] ERROR Application - Unhandled: POST - /api/upload
io.ktor.utils.io.charsets.MalformedInputException: Input length = 1
at io.ktor.utils.io.charsets.CharsetJVMKt.throwExceptionWrapped(CharsetJVM.kt:323)
at io.ktor.utils.io.charsets.CharsetJVMKt.decodeImplSlow(CharsetJVM.kt:289)
at io.ktor.utils.io.charsets.CharsetJVMKt.decodeExactBytes(CharsetJVM.kt:254)
at io.ktor.utils.io.core.StringsKt.readTextExactBytes(Strings.kt:293)
at io.ktor.utils.io.core.StringsKt.readTextExactBytes$default(Strings.kt:292)
at io.ktor.utils.io.core.AbstractInput.readText(AbstractInput.kt:468)
at io.ktor.utils.io.core.AbstractInput.readText$default(AbstractInput.kt:465)
at io.ktor.http.cio.CIOMultipartDataBase.partToData(CIOMultipartData.kt:127)
at io.ktor.http.cio.CIOMultipartDataBase$partToData$1.invokeSuspend(CIOMultipartData.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
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.base/java.lang.Thread.run(Thread.java:844)
Same for me with different stack trace (logging feature).
In my case disabling the logging feature is a suitable workaround.
stacktrace
io.ktor.utils.io.charsets.MalformedInputException: Input length = 1
at io.ktor.utils.io.charsets.CharsetJVMKt.throwExceptionWrapped(CharsetJVM.kt:323)
at io.ktor.utils.io.charsets.CharsetJVMKt.decode(CharsetJVM.kt:199)
at io.ktor.utils.io.charsets.EncodingKt.decode(Encoding.kt:101)
at io.ktor.utils.io.core.StringsKt.readText(Strings.kt:251)
at io.ktor.utils.io.core.StringsKt.readText$default(Strings.kt:250)
at io.ktor.client.features.logging.Logging$logRequestBody$2$1.invokeSuspend(Logging.kt:111)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:184)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:108)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:307)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:317)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:249)
at io.ktor.utils.io.ByteBufferChannel.resumeWriteOp(ByteBufferChannel.kt:2210)
at io.ktor.utils.io.ByteBufferChannel.bytesRead(ByteBufferChannel.kt:930)
at io.ktor.utils.io.ByteBufferChannel.readAsMuchAsPossible(ByteBufferChannel.kt:544)
at io.ktor.utils.io.ByteBufferChannel.readAvailable$suspendImpl(ByteBufferChannel.kt:606)
at io.ktor.utils.io.ByteBufferChannel.readAvailable(Unknown Source:0)
at io.ktor.utils.io.jvm.javaio.WritingKt.copyTo(Writing.kt:21)
at io.ktor.utils.io.jvm.javaio.WritingKt.copyTo$default(Writing.kt:12)
at io.ktor.client.engine.android.AndroidClientEngineKt.writeTo(AndroidClientEngine.kt:112)
at io.ktor.client.engine.android.AndroidClientEngine.execute(AndroidClientEngine.kt:79)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:83)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
I can confirm that disabling logging feature is a workaround for this issue.
Also in Ktor 1.3.1 logging does not work properly, for example, I am not able to see post request body in the logs, it only shows these lines.
METHOD: HttpMethod(value=POST)
COMMON HEADERS
-> User-Agent: android
-> Accept: application/json
-> Accept-Charset: UTF-8
CONTENT HEADERS
BODY Content-Type: application/json
BODY Content-Type: application/json; charset=UTF-8
BODY START
-
BODY END
But for Ktor 1.2.6 I used to be seeing them.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Most helpful comment
Same for me with different stack trace (logging feature).
In my case disabling the logging feature is a suitable workaround.
stacktrace io.ktor.utils.io.charsets.MalformedInputException: Input length = 1 at io.ktor.utils.io.charsets.CharsetJVMKt.throwExceptionWrapped(CharsetJVM.kt:323) at io.ktor.utils.io.charsets.CharsetJVMKt.decode(CharsetJVM.kt:199) at io.ktor.utils.io.charsets.EncodingKt.decode(Encoding.kt:101) at io.ktor.utils.io.core.StringsKt.readText(Strings.kt:251) at io.ktor.utils.io.core.StringsKt.readText$default(Strings.kt:250) at io.ktor.client.features.logging.Logging$logRequestBody$2$1.invokeSuspend(Logging.kt:111) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69) at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:184) at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:108) at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:307) at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:317) at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:249) at io.ktor.utils.io.ByteBufferChannel.resumeWriteOp(ByteBufferChannel.kt:2210) at io.ktor.utils.io.ByteBufferChannel.bytesRead(ByteBufferChannel.kt:930) at io.ktor.utils.io.ByteBufferChannel.readAsMuchAsPossible(ByteBufferChannel.kt:544) at io.ktor.utils.io.ByteBufferChannel.readAvailable$suspendImpl(ByteBufferChannel.kt:606) at io.ktor.utils.io.ByteBufferChannel.readAvailable(Unknown Source:0) at io.ktor.utils.io.jvm.javaio.WritingKt.copyTo(Writing.kt:21) at io.ktor.utils.io.jvm.javaio.WritingKt.copyTo$default(Writing.kt:12) at io.ktor.client.engine.android.AndroidClientEngineKt.writeTo(AndroidClientEngine.kt:112) at io.ktor.client.engine.android.AndroidClientEngine.execute(AndroidClientEngine.kt:79) at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:83) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)