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)
...
Другие вопросы по тегам