Использование Ktor HTTP Client (и kotlinx.io?) Для возобновляемых загрузок непосредственно в файл
Раньше я не работал с (большими) загрузками по протоколу HTTP, поэтому я не слишком разбираюсь в этой теме.
Итак, что я хочу сделать, так это программно загрузить файл из Интернета прямо в локальную файловую систему. Также было бы хорошо, если бы загрузка возобновлялась. Что касается Java, я нашел эту статью Baeldung, которая в основном объясняет, как именно это сделать.
Однако я работаю не с Java, а с Kotlin. В частности, я использую HTTP-клиент Ktor (с движком Apache, но при необходимости я могу его переключить). Хотелось бы узнать, есть ли возможность использовать этот клиент так же, как в упомянутой статье. По сути, я бы вызвал запрос GET, но я бы сказал клиенту передать байты в указанный
File
расположение. Когда загрузка прерывается, я бы запросил размер файла для текущего статуса и возобновил его с этой позиции байта (PartialContent).
Я узнал что
- Ktor сервер поддерживает это: https://ktor.io/docs/partial-content.html
- В
kotlinx-io
библиотека может оказаться полезной (из-за аналогии со статьей): https://github.com/Kotlin/kotlinx-io
Однако я не знаю, как собрать все вместе и "соединить незакрепленные провода", чтобы все заработало. Также кажется, что мой вопрос слишком конкретен для простого поиска в Google, поэтому сейчас я прибегаю к SO. Если бы кто-нибудь, имеющий некоторый опыт работы с Ktor и HTTP-загрузками, мог пролить свет на это для меня, я был бы очень признателен!
PS в дальнейшем может быть интересен ответ на этот ТАК вопрос.
Данные версии:
- Котлин /JVM 1.4.20
- Ктор 1.4.1
- OpenJDK 11
1 ответ
Основная идея состоит в том, чтобы получить общую длину контента, создав
HEAD
запрашивать, а затем итеративно загружать контент с помощью заголовка Range.Вот пример реализации:
suspend fun main() {
val client = HttpClient()
client.download(
"https://weq714976.live-website.com/wp-content/uploads/2019/03/WeQ-Influencers-Logo-1568x812.png",
File("result.png")
)
}
suspend fun HttpClient.download(url: String, outFile: File, chunkSize: Int = 1024) {
val length = head<HttpResponse>(url).headers[HttpHeaders.ContentLength]?.toLong() as Long
val lastByte = length - 1
var start = outFile.length()
val output = FileOutputStream(outFile, true)
while (true) {
val end = min(start + chunkSize - 1, lastByte)
get<HttpResponse>(url) {
header("Range", "bytes=${start}-${end}")
}.content.copyTo(output)
if (end >= lastByte) break
start += chunkSize
}
}