接收响应
所有用于发送 HTTP 请求的函数(如 request
、get
、post
等)都允许你以 HttpResponse
对象的形式接收响应。
HttpResponse
暴露了所需的 API,用于以各种方式(原始字节、JSON 对象等)获取响应体,以及获取响应参数,例如状态码、内容类型和标头。例如,你可以通过以下方式接收不带参数的 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()
要了解更多信息,请参见接收和发送数据。
流式数据
当你调用 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
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()
count += chunk.remaining
chunk.transferTo(stream)
println("Received $count bytes from ${httpResponse.contentLength()}")
}
}
}
println("A file saved to ${file.path}")
}
在此示例中,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}")
}