В Ktor, как я могу передать поток InputStream в тело запроса HttpClient?
Я использую Ktor 1.2.2, и у меня есть объект InputStream, который я хочу использовать в качестве тела для запроса HttpClient, который я делаю по линии. Вплоть до Ktor 0.95 существовал объект InputStreamContent, который, казалось, делал именно это, но был удален из Ktor в версии 1.0.0 (к сожалению, не мог понять, почему).
Я могу заставить его работать, используя ByteArrayContent (см. Код ниже), но я бы предпочел найти решение, которое не требует загрузки всего InputStream в память...
ByteArrayContent(input.readAllBytes())
Этот код представляет собой простой тестовый пример, который имитирует то, что я пытаюсь достичь:
val file = File("c:\\tmp\\foo.pdf")
val inputStream = file.inputStream()
val client = HttpClient(CIO)
client.call(url) {
method = HttpMethod.Post
body = inputStream // TODO: Make this work :(
}
// [... other code that uses the response below]
Дайте мне знать, если я пропустил любую соответствующую информацию,
Спасибо!
4 ответа
Единственный API (который я обнаружил...) в Ktor 1.2.2 - это потенциальная отправка запроса, состоящего из нескольких частей, который потребует от вашего принимающего сервера такой возможности, но он поддерживает прямой InputStream.
Из их документов:
val data: List<PartData> = formData {
// Can append: String, Number, ByteArray and Input.
append("hello", "world")
append("number", 10)
append("ba", byteArrayOf(1, 2, 3, 4))
append("input", inputStream.asInput())
// Allow to set headers to the part:
append("hello", "world", headersOf("X-My-Header" to "MyValue"))
}
При этом я не знаю, как это работает внутри, и, вероятно, все еще загружает в память весь поток.
Метод readBytes буферизуется, поэтому он не займет всю память.
inputStream.readBytes()
inputStream.close()
Как примечание, вы все равно должны закрыть inputStream большинством методов InputStreams
Источник Ktor: https://ktor.io/clients/http-client/call/requests.html
Котлин Источник: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-input-stream/index.html
Один из способов добиться этого - создать подкласс OutgoingContent.WriteChannelContent и установить его в теле вашего почтового запроса.
Пример может выглядеть так:
class StreamContent(private val pdfFile:File): OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
val readChannel = pdfFile.inputStream().channel
var copiedBytes: Long
do {
copiedBytes = readChannel.copyTo(channel, 1024)
} while (copiedBytes > 0)
}
override val contentType = ContentType.Application.Pdf
override val contentLength: Long = pdfFile.length()
}
// in suspend function
val pdfFile = File("c:\\tmp\\foo.pdf")
val client = HttpClient()
val result = client.post<HttpResponse>("http://upload.url") {
body = StreamContent(pdfFile)
}
Вот что у меня работает на Ktor 1.3.0 для загрузки файлов в GCP:
client.put<Unit> {
url(url)
method = HttpMethod.Put
body = ByteArrayContent(file.readBytes(), ContentType.Application.OctetStream)
}
Мне не удалось заставить работать решение от @stefan. Вот мой альтернативный пример (нацеленный на Ktor 2.2.2):
class StreamContent(private val pdfFile: File) : OutgoingContent.ReadChannelContent() {
override fun readFrom(): ByteReadChannel = pdfFile.readChannel()
override val contentType = ContentType.Application.Pdf
override val contentLength: Long = pdfFile.length()
}
// in suspend function
val pdfFile = File("c:\\tmp\\foo.pdf")
val client = HttpClient()
val result = client.post<HttpResponse>("http://upload.url") {
setBody(StreamContent(pdfFile))
}