レスポンスの受信
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ヘッダー値からのcharset用です。etagはE-Tagヘッダー値用です。setCookieはSet-Cookieヘッダー値用です。Ktor は、呼び出し間で Cookie を保持できる HttpCookies プラグインも提供しています。
レスポンスボディの受信
生のボディ
レスポンスの生のボディを受信するには、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()詳細については、データの送受信を参照してください。
ContentNegotiation プラグインは、クライアントとサーバーの両方で利用できます。ご自身のケースに適切なものを使用してください。
マルチパートフォームデータ
マルチパートフォームデータを含むレスポンスを受信する場合、そのボディを 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}")
}