接收响应
所有用于发送 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()要了解更多信息,请参见接收和发送数据。
Multipart 表单数据
当接收到包含 Multipart 表单数据的响应时,你可以将其体读取为 MultiPartData 实例。 这允许你处理响应中包含的表单字段和文件。
下面的示例演示了如何处理来自 multipart 响应的文本表单字段和文件上传:
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}")
}