Problem translating retrofit multipart call to ktor

135 Views Asked by At

Really stuck with translating multipart request from retrofit to ktor client.

In retrofit I have following method:

 @Multipart
 @POST("rest/sendPhotos")
 suspend fun sendPhotos(@Part files: List<MultipartBody.Part>): Response<ResultDto>

in datasource layer it's called as:

fun sendFiles(files: List<File>): Response<ResultDto> {
    val parts = mutableListOf<MultipartBody.Part>()
    files.forEachIndexed { index, file ->
        val requestFile = file.asRequestBody("multipart/form-items".toMediaTypeOrNull())
        val part = MultipartBody.Part.createFormData("image_$index", file.name, requestFile)
        parts.add(part)
    }
    return mainApiService.sendPhotos(parts)
}

In ktor I'm trying to translate it as:

    private suspend fun sendFiles(files: List<File>): HttpResponse {
        val parts = mutableListOf<PartData>()
        files.forEachIndexed { index, file ->
            val part = formData {
                append("content", InputProvider(file.length()) { file.inputStream().asInput() }, Headers.build {
                    append(HttpHeaders.ContentType, "multipart/form-items")
                    append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"; name=\"image_$index\"")
                })
            }
            parts.addAll(part)
        }
        return httpClient.post("rest/sendPhotos") {
            setBody(MultiPartFormDataContent(
                parts = parts,
            ))
        }
    }

But it doesn't work... Tried zillion variants, but helpless. Trying to fix it several days, unfortunately ktor logging can't help me, since it doesn't show body, printing smth like: [request body omitted]

Can anyone please help me? Any ideas or clues?

Added Retrofit logger reads:

Content-Type: multipart/form-data; boundary=0574af17-6a38-4888-af48-b368c4ce0f79
17:57:03.773  D  Content-Length: 209034
17:57:03.773  D  ticket: 00204e0d1669ccee81e6410390e48e530ba80c0c
17:57:03.788  D  
17:57:03.793  D  --0574af17-6a38-4888-af48-b368c4ce0f79
                 Content-Disposition: form-data; name="image_0"; filename="2024-01-03-00-56-48-232.jpg"
                 Content-Type: multipart/form-items
                 Content-Length: 208249

             

I can't get similar logs from ktor, it shows nothing except logs omitted - it's ugly...

2

There are 2 best solutions below

1
Aleksei Tirman On BEST ANSWER

According to the Retrofit logs, the equivalent Ktor code should be the following:

client.post("rest/sendPhotos") {
    setBody(MultiPartFormDataContent(formData {
        files.forEachIndexed { index, file ->
            append("image_$index", file.readBytes(), Headers.build {
                append(HttpHeaders.ContentType, "multipart/form-items")
                append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
            })
        }
    }))
}
2
Rakib Hasan On

I think there are a couple of issues in your Ktor code that might be causing the problem, I'm rewriting the code, here it is -

private suspend fun sendFiles(files: List<File>): HttpResponse {
    val parts = mutableListOf<PartData>()
    files.forEachIndexed { index, file ->
        val part = formData {
            appendInput(
                "files",
                file.name,
                InputProvider { file.inputStream().asInput() },
                headersOf(
                    HttpHeaders.ContentType to listOf(ContentType.Application.OctetStream.toString()),
                    HttpHeaders.ContentDisposition to listOf(
                        ContentDisposition.File.withParameter(ContentDisposition.Parameters.Name, "image_$index")
                            .toString()
                    )
                )
            )
        }
        parts.add(part)
    }
    return httpClient.post("rest/sendPhotos") {
        setBody(MultiPartFormDataContent(parts))
    }
}

I Used appendInput instead of append in formData to handle file input correctly. Also, ContentType.Application.OctetStream for file content type. Furthermore, ContentDisposition.File to handle file disposition correctly.

I assume that the server is expecting the files to be sent as "files". You might need to adjust the files part in appendInput based on your server requirements.

And also, make sure to check the server documentation for the correct field names and types. If the issue persists, you might want to inspect the server logs to see if there are any error messages or to verify that the request is being received as expected.

Hope it helps!