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