Ktor http client - ход выполнения запроса
Как отслеживать ход выполнения запроса в http-клиенте Ktor?
Например: у меня есть такой запрос:
val response = HttpClient().get<String>("https://stackru.com/")
и я хочу отслеживать ход выполнения запроса с помощью индикатора выполнения, например:
fun progress(downloaded: Long, contentLength: Long) {
// Update progress bar or whatever
}
Как установить progress()
быть вызванным HttpClient?
изменить: это мультиплатформенный проект Kotlin. Соответствующие зависимости:
implementation 'io.ktor:ktor-client-core:1.2.5'
implementation 'io.ktor:ktor-client-cio:1.2.5'
3 ответа
Начиная с Ktor 1.6.0, вы можете реагировать на изменение хода загрузки с помощью расширения функцииonDownload, предоставляемой
HttpRequestBuilder
:
val channel = get<ByteReadChannel>("https://ktor.io/") {
onDownload { bytesSentTotal, contentLength ->
println("Received $bytesSentTotal bytes from $contentLength")
}
}
Также есть функция onUpload, которую можно использовать для отображения прогресса загрузки:
onUpload { bytesSentTotal, contentLength ->
println("Sent $bytesSentTotal bytes from $contentLength")
}
Вот рабочие примеры из документации Ktor:
Как передать прогресс загрузки в поток?
Я хочу наблюдать за процессом загрузки с помощью потока, поэтому я пишу такую функцию:
suspend fun downloadFile(file: File, url: String): Flow<Int>{
val client = HttpClient(Android)
return flow{
val httpResponse: HttpResponse = client.get(url) {
onDownload { bytesSentTotal, contentLength ->
val progress = (bytesSentTotal * 100f / contentLength).roundToInt()
emit(progress)
}
}
val responseBody: ByteArray = httpResponse.receive()
file.writeBytes(responseBody)
}
}
но
onDownload
будет вызван только один раз. Если я удалю
emit(progress)
это будет работать.
@andrey-aksyonov
В Ktor<1.3.2 вы можете отслеживать прогресс загрузки, запрашивая ByteReadChannel вместо String.
Обратной стороной является то, что таким образом вы не получите размер содержимого вашего файла, но его можно легко получить с помощью отдельного запроса HEAD (который не должен добавлять слишком много накладных расходов, если вы загружаете файл, который достаточно велик, чтобы требовать прогресса. мониторинг)
val contentLength = // need to get via HEAD reqeuest
suspend fun download() {
val url = "https://stackru.com/"
val client = HttpClient()
val channel = client.get<ByteReadChannel>(url)
var total = 0
var readBytes:Int
var buffer = ByteArray(contentLength)
do {
readBytes = channel.readAvailable(buffer, total, 4096 )
total+=readBytes
progress(total, contentLength)
} while (readBytes>0)
val response = String(buffer)
}
Для Ktor>1.3.2 рекомендуется способ контроля за ходом запроса является использование HttpStatement, например:
suspend fun download() {
val client = HttpClient()
val builder = HttpRequestBuilder().apply {
url("https://stackru.com")
}
val httpStatement = HttpStatement(builder, client)
httpStatement.execute { response: HttpResponse ->
// Response is not downloaded here
val channel = response.receive<ByteReadChannel>()
val contentLength = response.contentLength()
requireNotNull(contentLength) {"Header needs to be set by server"}
var total = 0
var readBytes:Int
var buffer = ByteArray(contentLength)
do {
readBytes = channel.readAvailable(buffer, total, 4096 )
total+=readBytes
progress(total, contentLength)
} while (readBytes>0)
val response = String(buffer)
}
}
Конечно, при загрузке большого файла было бы разумнее использовать буфер меньшего размера и напрямую записывать его в какой-либо файл, например:
...
var buffer = ByteArray(4096)
do {
readBytes = channel.readAvailable(buffer, 0, 4096 )
total+=readBytes
writeToFile(buffer, readBytes) // do something sensible with the read bytes
progress(total, response.contentLength())
} while (readBytes>0)
...