https://kotlinlang.org logo
#ktor
Title
# ktor
k

Kyle

10/10/2023, 7:24 PM
I’m trying to send a multipart request in a multiplatform project from swift.
Copy code
suspend fun createCert(...) {
    //bunch of other logic here  
      val multipart = MultiPartFormDataContent(formData {
            append("certification", cert, Headers.build {
                append(HttpHeaders.ContentType, "application/json")
            })
            append("photo", certImage, Headers.build {
                append(HttpHeaders.ContentType, "image/png")
                append(HttpHeaders.ContentDisposition, "filename=$filename")
            })
        })
        val newCert = apiClient.createCertification(multipart)
        return newCert.id
but every time I call it I keep getting
<http://io.ktor.utils.io|io.ktor.utils.io>.errors.EOFException: Premature end of stream: expected 3081 bytes
because of the photo. At the point I’m appending certImage it’s a ByteArray. I’m using ktorfit for the client
Copy code
@POST("/certification")
    suspend fun createCertification(
        @Body map: MultiPartFormDataContent
    ): UserCertification
Any ideas why it would be failing consistently?
a

Aleksei Tirman [JB]

10/11/2023, 5:01 AM
Can you reproduce this problem with just Ktor?
t

tylerwilson

10/28/2023, 6:00 PM
I am getting this too but only on iOS using just altor. Have you found a solution? Or what the issue is?
k

Kyle

10/28/2023, 8:16 PM
I wasn’t sending the correct bytearray object. I was attempting to send the swiftui Transferrable object in a bytearray and it wasn’t having any of that.
Copy code
if let data = try? await certificationImageItem?.loadTransferable(type: Data.self) {
                        if let uiImage:UIImage = UIImage(data: data) {
                            let finalImage: Data = uiImage.jpegData(compressionQuality: 0.5)!
                            let imageByteArray = NSDataByteArrayKt.NSDatatoByteArray(data: finalImage)
                        }
I removed a bunch of my irrelivant code when pasting so I’m not 100% sure that that’ll compile correctly but it’s the important bits. and then in NSDataByteArrayKt,
Copy code
@OptIn(ExperimentalForeignApi::class)
fun NSDatatoByteArray(data: NSData): ByteArray = ByteArray(data.length.toInt()).apply {
    usePinned {
        memcpy(it.addressOf(0), data.bytes, data.length)
    }
}
In theory you could do that as an extension, like
Copy code
@OptIn(ExperimentalForeignApi::class)
fun NSData.toByteArray(): ByteArray = ByteArray(this@toByteArray.length.toInt()).apply {
    usePinned {
        memcpy(it.addressOf(0), this@toByteArray.bytes, this@toByteArray.length)
    }
}
but it didn’t work for me on the first try and I haven’t gone back to revisit it.
t

tylerwilson

10/30/2023, 1:05 PM
Thank you for the feedback. That is more or less what I am doing - I send a UIImage down to the iosMain side of my KMP module, it does the jpeg encoding to NSData, I do the same conversion to ByteArray and then add to the multipart form data. Bits look like:
Copy code
val data = formData {
            append("\"p_comId\"", "${auth.companyId}")
            append("\"p_customerId\"", "$customerId")
            append("\"p_type\"", type)
            append("\"p_image\"", buildPacket {
                writeFully(image.asByteArray())
            }, Headers.build {
                append(HttpHeaders.ContentDisposition, "filename=\"${image.name()}\"")
                append(HttpHeaders.ContentType, "image/jpeg")
            })
}
and the 'ImageWrapper' for iOS:
Copy code
constructor(image: UIImage, name: String) {
        val compression: CGFloat = 0.7 as CGFloat
        this.data = UIImageJPEGRepresentation(image, compression)
        this.name = "$name.jpg"
    }

    actual fun name(): String {
        return this.name ?: "Unknown"
    }

    actual fun size(): Int {
        return data?.length?.toInt() ?: 0
    }

    @OptIn(ExperimentalForeignApi::class)
    actual fun asByteArray(): ByteArray {
        return ByteArray(size()).apply {
            usePinned {pinned ->
                data?.let {
                    memcpy(pinned.addressOf(0), it.bytes, it.length)
                }
            }
        }
    }
still no luck. trying to perhaps reduce the size of the image before sending, hoping that helps.
FYI, found the issue listed here: https://youtrack.jetbrains.com/issue/KTOR-6281/Darwin-EOFException-when-sending-multipart-data-using-Ktor-2.3.4 I reverted to Ktor 2.3.3 and it is working again. Guess I will have to wait for 3.x for the official fix. Thanks again for your input.
4 Views