Ktor: MalformedInputException: Input length = 1, when uploading an image multipart

Created on 1 Apr 2020  路  5Comments  路  Source: ktorio/ktor

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:

  1. Use Retrofit to Upload an image to a Ktor server.
  2. call receiveMultipart() to receive MP on the Ktor side.
  3. See error

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)
bug

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)

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KennethanCeyer picture KennethanCeyer  路  4Comments

diaodou picture diaodou  路  3Comments

guenhter picture guenhter  路  4Comments

dedward3 picture dedward3  路  4Comments

gabin8 picture gabin8  路  3Comments