首先给大家一段官方文档里的代码,来看看用 Jtor-Client 如何上传文件:
官方文档可以注意到这里有一个关键的代码是 inputStream.asInput()
,这个代码在 JVM 的 Target 下很容易构造,我们只需要使用 FileInputStream
就万事大吉了,复杂点的可以写成这样:
private fun buildPostFileBody(params: Map<String, String>, files: Map<String, String>) = MultiPartFormDataContent(
formData {
params.forEach { (t, u) -> append(t, u) }
files.forEach { (t, u) ->
appendInput(t, headersOf("Content-Type", "application/octet-stream")) {
FileInputStream(File(u)).asInput()
}
}
}
)
然鹅在 Kotlin/Native 下,没有 FileStream
了,也就没有这样简单的 asInput()
的操作。一开始我也进行了搜索,没找到相应的资料,相反的也找到有一些人,甚至是老外说,Ktor-Client 在 Native 下是有缺陷的,比如说不能上传文件。
当然我并不会认同这些,没有资料那就要自己研究了,在上一篇文章里(点击阅读),我讲了如何在 K/N 下包装出形同 JVM 的 File
类,我们的解决方案也是由此而来的。
下面简单说一下思路,首先 Input
是一个接口,所以我们必然要找 Input 的实现类,然后再去看实现类里有没有什么可以用的东西。
我们可以瞬间锁定 ByteReadPacket
和 IoBuffer
这两个类,至于中间的那个是抽象类,应该不是我们要的,那就一个个来看吧,首先还是先把文件操作封装下,目前只要读取文件的操作。
actual fun readContent(filePath: String) = memScoped {
val st = alloc<stat>()
stat(filePath, st.ptr)
val size = st.st_size
val buf = allocArray<ByteVar>(size)
val f = fopen(filePath, "rb")
fread(buf, 1UL, size.toULong(), f)
fclose(f)
buf.readBytes(size.toInt())
}
用这个函数可以得到一个 Kotlin 的 ByteArray
类型的对象,所以我们可以这样来获得 ByteReadPacket
和 IoBuffer
实例:
val input1 = ByteReadPacket(readContent("a.txt"))
val input2 = readContent("a.txt").let { IoBuffer(it.toCValues().ptr, it.size) }
这两种做法有什么不同? 最本质的不同在于,ByteReadPacket
在 common
里实现,因此可以把相关的代码写在 common
内,而 IoBuffer
是区分平台实现,所以需要在各个平台的 target 里都写一遍,所以为了方便起见,我最终选择了使用 ByteReadPacket
。
最终实现的代码如下:
expect class File {
... ...
fun readContent(): ByteArray
... ...
}
fun File.asInput() = ByteReadPacket(readContent())
这样,就可以在 Ktor-Client 内直接使用 asInput()
了,和 JVM 下一样方便:
private fun buildPostFileBody(params: Map<String, String>, files: Map<String, String>) = MultiPartFormDataContent(
formData {
params.forEach { (t, u) -> append(t, u) }
files.forEach { (t, u) ->
appendInput(t, headersOf("Content-Type", "application/octet-stream")) {
File(u).asInput()
}
}
}
)
最后再说一句,Ktor 的文档真的让人无语(戳此处去官方看看),还请多少给点示例吧,不然真的是面向运气Coding,全靠猜。
网友评论