0

I am looking for solution to upload large files(1 GB or more) in KMM using Ktor. If i use ByteArray its not efficient and produces out of memory issue. Is There any way in ktor to upload files using file's local url(saved on device).

I tried formData {} request in Ktor. But in common code not able to acccess File class from java.

1 Answer 1

0

You can add to the common code the following expected declaration of a function, which returns an OutgoingContent by using the file contents as a source for the body:

expect suspend fun bodyFromFile(filepath: String): OutgoingContent

Here's the usage of the above function:

val client = HttpClient()
val response = client.post("https://httpbin.org/post") {
    setBody(bodyFromFile("path/to/file"))
}

For JVM and Android, you can use the Ktor's File.readChannel method to read the contents of a file to ByteReadChannel:

actual suspend fun bodyFromFile(filepath: String): OutgoingContent {
    return object : OutgoingContent.ReadChannelContent() {
        override val contentType: ContentType
            get() = ContentType.defaultForFilePath(filepath)
        override fun readFrom(): ByteReadChannel {
            return File(filepath).readChannel()
        }
    }
}

For iOS, you can use the dispatch_read function to asynchronously read the file contents and write that data to the ByteWriteChannel exposed by the ChannelWriterContent class:

private const val BUFFER_SIZE = 4096

@OptIn(ExperimentalForeignApi::class)
actual suspend fun bodyFromFile(filepath: String): OutgoingContent {
    val queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0u)

    suspend fun read(fd: Int): NSData? {
        return suspendCancellableCoroutine { continuation ->
            dispatch_read(fd, BUFFER_SIZE.toULong(), queue) { dispatchData, _ ->
                val data = dispatchData as NSData
                continuation.resume(if (data.bytes != null) data else null)
            }
        }
    }

    return ChannelWriterContent(contentType = ContentType.defaultForFilePath(filepath), body = {
        val fd = open(filepath, O_RDWR)
        try {
            while (true) {
                val data = read(fd) ?: break
                val bytes: CPointer<ByteVar> = data.bytes!!.reinterpret()
                writeFully(bytes, 0, data.length.toInt())
                flush()
            }
        } finally {
            close(fd)
        }
    })
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.