接收回應
所有用於發出 HTTP 請求的函數(如 request、get、post 等)都允許您將回應接收為 一個 HttpResponse 物件。
HttpResponse 提供了取得回應主體的各種方式(原始位元組、JSON 物件等)所需 API,並可獲取回應參數,例如狀態碼、內容類型及標頭。 例如,您可以透過以下方式為不帶參數的 GET 請求接收 HttpResponse:
val response: HttpResponse = client.get("https://ktor.io/docs/welcome.html")接收回應參數
HttpResponse 類別允許您獲取各種回應參數,例如狀態碼、標頭、HTTP 版本等。
狀態碼
若要獲取回應的狀態碼,請使用 HttpResponse.status 屬性:
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:
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.body()同樣地,您可以將主體獲取為 ByteArray:
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val byteArrayBody: ByteArray = httpResponse.body()以下可執行範例 展示了如何將回應獲取為 ByteArray 並儲存到檔案中:
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 資料反序列化為資料類別:
val customer: Customer = client.get("http://localhost:8080/customer/3").body()若要了解更多資訊,請參閱接收和傳送資料。
多部分表單資料
當您接收包含多部分表單資料的回應時,您可以將其主體讀取為 MultiPartData 實例。 這允許您處理回應中包含的表單欄位和檔案。
以下範例展示了如何處理多部分回應中的文字表單欄位和檔案上傳:
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 屬性存取:
when (part) {
is PartData.FormItem -> {
println("Form item key: ${part.name}")
val value = part.value
// ...
}
}檔案上傳
PartData.FileItem 表示一個檔案項目。您可以將檔案上傳處理為位元組串流:
when (part) {
is PartData.FileItem -> {
println("file: ${part.name}")
println(part.originalFileName)
val fileContent: ByteReadChannel = part.provider()
// ...
}
}資源清理
表單處理完成後,每個部分都會使用 .dispose() 函數來釋放資源。
part.dispose()串流資料
當您呼叫 HttpResponse.body 函數來獲取主體時,Ktor 會在記憶體中處理回應並傳回完整的回應主體。如果您需要依序取得回應的區塊,而不是等待整個回應,請使用具有範圍 execute 區塊的 HttpStatement。 以下可執行範例 展示了如何以區塊(位元組封包)形式接收回應內容並將其儲存到檔案中:
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 通道與
RawSink、RawSource或OutputStream等類型之間轉換,請參閱 I/O 互通性。
在此範例中,ByteReadChannel 用於非同步讀取資料。使用 ByteReadChannel.readRemaining() 可擷取通道中所有可用的位元組,而 Source.transferTo() 則直接將資料寫入檔案,減少不必要的記憶體分配。
若要將回應主體儲存到檔案而不進行額外處理,您可以改用 ByteReadChannel.copyAndClose() 函數:
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}")
}