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

Fail

09/11/2019, 12:18 PM
Hi all! I have a theoretical question. I want to send files from multiplatform client to serverver. I saw an example.
Copy code
val result = <http://client.post|client.post><HttpResponse>("<http://127.0.0.1:$port/handler>") {
                    body = MultiPartContent.build {
                        add("user", "myuser")
                        add("password", "password")
                        add("file", byteArrayOf(1, 2, 3, 4), filename = "binary.bin")
                    }
                }
But what will doing if file is big? ByteArraySize could not be more than free RAM. How to send file from IOS and Android to multiplatform without out of memory errors?
r

ribesg

09/11/2019, 12:38 PM
Here’s a working snippet from my codebase:
Copy code
private suspend fun doUploadFile(data: ByteReadChannel, uploadUrl: String, contentType: ContentType): Unit =
    http.put(uploadUrl) {
        body = object : OutgoingContent.ReadChannelContent() {
            override val contentType = contentType
            override fun readFrom() = data
        }
    }
(Used and working on both Android and iOS)
f

Fail

09/11/2019, 12:39 PM
Wow! Great, thank you! I'm going to try now
Can you get me example to call this method from IOS and Android?
r

ribesg

09/11/2019, 12:42 PM
On Android, with the
Uri
to a local file, the
data
parameter is
appCtx.contentResolver.openInputStream(uri)?.toByteReadChannel() ?: error(...)
For the
ContentType
I’m using
ContentType.fromFilePath(fileName).first()
f

Fail

09/11/2019, 12:45 PM
My regards! Thank you so much
For an example for IOS I will be even more grateful
r

ribesg

09/11/2019, 12:46 PM
I was about to tell you that it’s the same on iOS but after checking it was a little complex:
Copy code
private fun NSURL.toByteReadChannel(errorHandler: (Throwable) -> Unit): ByteReadChannel =
        writer(CoroutineExceptionHandler { _, e -> errorHandler(e) }, true) {
            memScoped {
                val bufferSize = 8192
                val buffer = allocArray<UByteVar>(bufferSize)
                val input = NSInputStream.inputStreamWithURL(this@toByteReadChannel)
                        ?: throw Error("Failed to create NSInputStream from url ${this@toByteReadChannel}")
                var result: Int
                try {
                    input.open()
                    do {
                        @Suppress("EXPERIMENTAL_API_USAGE")
                        result = input.read(buffer, bufferSize.toULong()).toInt()
                        if (result > 0) {
                            channel.writeFully(buffer.readBytes(bufferSize))
                        }
                        if (result < 0) {
                            throw Error("Failed to read inputStream: ${input.streamError?.debugDescription}")
                        }
                    } while (result > 0)
                } finally {
                    channel.close()
                    input.close()
                }
            }
        }.channel
f

Fail

09/11/2019, 12:48 PM
You just saved my day. Now I feel like a lazy ass because I haven’t tried it myself
😉
4 Views