Skip to content

接收回應

所有用於發出 HTTP 請求的函數(如 requestgetpost 等)都允許您將回應接收為 一個 HttpResponse 物件。

HttpResponse 提供了取得回應主體的各種方式(原始位元組、JSON 物件等)所需 API,並可獲取回應參數,例如狀態碼、內容類型及標頭。 例如,您可以透過以下方式為不帶參數的 GET 請求接收 HttpResponse

kotlin
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")

接收回應參數

HttpResponse 類別允許您獲取各種回應參數,例如狀態碼、標頭、HTTP 版本等。

狀態碼

若要獲取回應的狀態碼,請使用 HttpResponse.status 屬性:

kotlin
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*

val httpResponse: HttpResponse = client.get("https://ktor.io/")
if (httpResponse.status.value in 200..299) {
    println("Successful response!")
}

標頭

HttpResponse.headers 屬性允許您獲取包含所有回應標頭的 Headers 對映。此外,HttpResponse 提供了以下用於接收特定標頭值的函數:

  • contentType 用於 Content-Type 標頭值
  • charset 用於 Content-Type 標頭值中的字元集。
  • etag 用於 E-Tag 標頭值。
  • setCookie 用於 Set-Cookie 標頭值。

    Ktor 還提供了 HttpCookies 外掛程式,允許您在呼叫之間保留 Cookie。

接收回應主體

原始主體

若要接收回應的原始主體,請呼叫 body 函數並將所需類型作為參數傳遞。以下程式碼片段展示了如何將原始主體接收為 String

kotlin
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.body()

同樣地,您可以將主體獲取為 ByteArray

kotlin
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val byteArrayBody: ByteArray = httpResponse.body()

以下可執行範例 展示了如何將回應獲取為 ByteArray 並儲存到檔案中:

kotlin
    val client = HttpClient()
    val file = File.createTempFile("files", "index")

    runBlocking {
        val httpResponse: HttpResponse = client.get("https://ktor.io/") {
            onDownload { bytesSentTotal, contentLength ->
                println("Received $bytesSentTotal bytes from $contentLength")
            }
        }
        val responseBody: ByteArray = httpResponse.body()
        file.writeBytes(responseBody)
        println("A file saved to ${file.path}")
    }

onDownload() 在上述範例中的擴充函數用於顯示下載進度。

對於非串流請求,回應主體會自動載入並快取到記憶體中,允許重複存取。雖然這對於小型負載是高效的,但對於大型回應可能會導致高記憶體使用量。

若要有效率地處理大型回應,請使用串流方式,這會在不將回應儲存到記憶體的情況下,以增量方式處理回應。

JSON 物件

在安裝 ContentNegotiation 外掛程式後,您可以在接收回應時將 JSON 資料反序列化為資料類別:

kotlin
val customer: Customer = client.get("http://localhost:8080/customer/3").body()

若要了解更多資訊,請參閱接收和傳送資料

ContentNegotiation 外掛程式適用於用戶端伺服器。請確保根據您的情況使用正確的外掛程式。

多部分表單資料

當您接收包含多部分表單資料的回應時,您可以將其主體讀取為 MultiPartData 實例。 這允許您處理回應中包含的表單欄位和檔案。

以下範例展示了如何處理多部分回應中的文字表單欄位和檔案上傳:

kotlin
val response = client.post("https://myserver.com/multipart/receive")

val multipart = response.body<MultiPartData>()

multipart.forEachPart { part ->
    when (part) {
        is PartData.FormItem -> {
            println("Form item key: ${part.name}")
            val value = part.value
            // ...
        }
        is PartData.FileItem -> {
            println("file: ${part.name}")
            println(part.originalFileName)
            val fileContent: ByteReadChannel = part.provider()
            // ...
        }
    }
    part.dispose()
}

表單欄位

PartData.FormItem 表示一個表單欄位,其值可透過 value 屬性存取:

kotlin
when (part) {
    is PartData.FormItem -> {
        println("Form item key: ${part.name}")
        val value = part.value
        // ...
    }
}

檔案上傳

PartData.FileItem 表示一個檔案項目。您可以將檔案上傳處理為位元組串流:

kotlin
when (part) {
    is PartData.FileItem -> {
        println("file: ${part.name}")
        println(part.originalFileName)
        val fileContent: ByteReadChannel = part.provider()
        // ...
    }
}

資源清理

表單處理完成後,每個部分都會使用 .dispose() 函數來釋放資源。

kotlin
part.dispose()

串流資料

當您呼叫 HttpResponse.body 函數來獲取主體時,Ktor 會在記憶體中處理回應並傳回完整的回應主體。如果您需要依序取得回應的區塊,而不是等待整個回應,請使用具有範圍 execute 區塊的 HttpStatement。 以下可執行範例 展示了如何以區塊(位元組封包)形式接收回應內容並將其儲存到檔案中:

kotlin
    val client = HttpClient(CIO)
    val file = File.createTempFile("files", "index")
    val stream = file.outputStream().asSink()
    val fileSize = 100 * 1024 * 1024
    val bufferSize = 1024 * 1024

    runBlocking {
        client.prepareGet("https://httpbin.org/bytes/$fileSize").execute { httpResponse ->
            val channel: ByteReadChannel = httpResponse.body()
            var count = 0L
            stream.use {
                while (!channel.exhausted()) {
                    val chunk = channel.readRemaining(bufferSize)
                    count += chunk.remaining

                    chunk.transferTo(stream)
                    println("Received $count bytes from ${httpResponse.contentLength()}")
                }
            }
        }

        println("A file saved to ${file.path}")
    }

若要在 Ktor 通道與 RawSinkRawSourceOutputStream 等類型之間轉換,請參閱 I/O 互通性

在此範例中,ByteReadChannel 用於非同步讀取資料。使用 ByteReadChannel.readRemaining() 可擷取通道中所有可用的位元組,而 Source.transferTo() 則直接將資料寫入檔案,減少不必要的記憶體分配。

若要將回應主體儲存到檔案而不進行額外處理,您可以改用 ByteReadChannel.copyAndClose() 函數:

Kotlin
client.prepareGet("https://httpbin.org/bytes/$fileSize").execute { httpResponse ->
    val channel: ByteReadChannel = httpResponse.body()
    channel.copyAndClose(file.writeChannel())
    println("A file saved to ${file.path}")
}