0
# Response Handling and Processing
1
2
Response processing capabilities for extracting data from HTTP responses, handling different content types, and streaming response data with type-safe APIs.
3
4
## Capabilities
5
6
### HttpResponse Interface
7
8
Main interface for representing HTTP responses with access to status, headers, and body content.
9
10
```kotlin { .api }
11
/**
12
* HTTP response representation providing access to status, headers, and body
13
*/
14
interface HttpResponse {
15
/** Associated HTTP client call */
16
val call: HttpClientCall
17
18
/** HTTP status code */
19
val status: HttpStatusCode
20
21
/** HTTP protocol version */
22
val version: HttpProtocolVersion
23
24
/** Request timestamp */
25
val requestTime: GMTDate
26
27
/** Response timestamp */
28
val responseTime: GMTDate
29
30
/** Response headers */
31
val headers: Headers
32
33
/** Coroutine context for the response */
34
override val coroutineContext: CoroutineContext
35
}
36
```
37
38
**Usage Examples:**
39
40
```kotlin
41
import io.ktor.client.*
42
import io.ktor.client.request.*
43
import io.ktor.client.statement.*
44
import io.ktor.http.*
45
46
val client = HttpClient()
47
48
val response = client.get("https://api.example.com/users")
49
50
// Access response properties
51
println("Status: ${response.status}")
52
println("Content-Type: ${response.headers["Content-Type"]}")
53
println("Request time: ${response.requestTime}")
54
println("Response time: ${response.responseTime}")
55
56
// Check status
57
if (response.status.isSuccess()) {
58
val content = response.bodyAsText()
59
println("Success: $content")
60
} else {
61
println("Error: ${response.status.description}")
62
}
63
64
// Check specific status codes
65
when (response.status) {
66
HttpStatusCode.OK -> println("Success")
67
HttpStatusCode.NotFound -> println("Resource not found")
68
HttpStatusCode.Unauthorized -> println("Authentication required")
69
else -> println("Status: ${response.status.value}")
70
}
71
```
72
73
### HttpStatement Class
74
75
Prepared HTTP statement for deferred execution and streaming response handling.
76
77
```kotlin { .api }
78
/**
79
* Prepared HTTP statement for deferred execution with streaming support
80
*/
81
class HttpStatement(
82
private val builder: HttpRequestBuilder,
83
private val client: HttpClient
84
) {
85
/** Execute statement and handle response with custom block */
86
suspend fun <T> execute(block: suspend (response: HttpResponse) -> T): T
87
88
/** Execute statement and deserialize response body to specified type */
89
suspend inline fun <reified T> body(): T
90
91
/** Execute statement and get response body as text */
92
suspend fun bodyAsText(fallbackCharset: Charset = Charsets.UTF_8): String
93
94
/** Execute statement and get response body as byte read channel */
95
suspend fun bodyAsChannel(): ByteReadChannel
96
97
/** Execute statement and get response body as byte array */
98
suspend fun bodyAsBytes(): ByteArray
99
}
100
```
101
102
**Usage Examples:**
103
104
```kotlin
105
import io.ktor.client.*
106
import io.ktor.client.statement.*
107
import io.ktor.utils.io.*
108
109
val client = HttpClient()
110
111
// Prepare statement for reuse
112
val statement = client.prepareGet("https://api.example.com/data")
113
114
// Execute with custom response handling
115
val result = statement.execute { response ->
116
when {
117
response.status.isSuccess() -> {
118
"Success: ${response.bodyAsText()}"
119
}
120
response.status.value == 404 -> {
121
"Resource not found"
122
}
123
else -> {
124
"Error: ${response.status.description}"
125
}
126
}
127
}
128
129
// Direct body extraction
130
val text = statement.bodyAsText()
131
val bytes = statement.bodyAsBytes()
132
133
// Streaming response
134
val channel = statement.bodyAsChannel()
135
val buffer = ByteArray(1024)
136
while (!channel.isClosedForRead) {
137
val bytesRead = channel.readAvailable(buffer)
138
if (bytesRead > 0) {
139
// Process buffer data
140
processChunk(buffer, bytesRead)
141
}
142
}
143
```
144
145
### Response Body Extraction
146
147
Extension functions for extracting response body content in various formats.
148
149
```kotlin { .api }
150
/**
151
* Deserialize response body to specified type using installed content negotiation
152
* @return Deserialized object of type T
153
*/
154
suspend inline fun <reified T> HttpResponse.body(): T
155
156
/**
157
* Get response body as text with optional charset fallback
158
* @param fallbackCharset Charset to use if not specified in response
159
* @return Response body as string
160
*/
161
suspend fun HttpResponse.bodyAsText(
162
fallbackCharset: Charset = Charsets.UTF_8
163
): String
164
165
/**
166
* Get response body as byte read channel for streaming
167
* @return ByteReadChannel for streaming response content
168
*/
169
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel
170
171
/**
172
* Get response body as complete byte array
173
* @return Response body as ByteArray
174
*/
175
suspend fun HttpResponse.bodyAsBytes(): ByteArray
176
177
/**
178
* Read complete response body as byte array (alias for bodyAsBytes)
179
* @return Response body as ByteArray
180
*/
181
suspend fun HttpResponse.readBytes(): ByteArray
182
183
/**
184
* Read complete response body as text (alias for bodyAsText)
185
* @param fallbackCharset Charset fallback
186
* @return Response body as string
187
*/
188
suspend fun HttpResponse.readText(
189
fallbackCharset: Charset = Charsets.UTF_8
190
): String
191
```
192
193
**Usage Examples:**
194
195
```kotlin
196
import io.ktor.client.*
197
import io.ktor.client.request.*
198
import io.ktor.client.statement.*
199
import io.ktor.client.call.*
200
import kotlinx.serialization.Serializable
201
202
val client = HttpClient {
203
install(ContentNegotiation) {
204
json()
205
}
206
}
207
208
@Serializable
209
data class User(val id: Long, val name: String, val email: String)
210
211
@Serializable
212
data class ApiResponse<T>(val data: T, val status: String)
213
214
// Deserialize JSON response
215
val response = client.get("https://api.example.com/users/123")
216
val user: User = response.body()
217
println("User: ${user.name}")
218
219
// Generic API response
220
val apiResponse: ApiResponse<User> = response.body()
221
println("Status: ${apiResponse.status}, User: ${apiResponse.data.name}")
222
223
// Text response
224
val textResponse = client.get("https://api.example.com/status")
225
val statusText = textResponse.bodyAsText()
226
println("Status: $statusText")
227
228
// Binary response
229
val imageResponse = client.get("https://example.com/image.png")
230
val imageBytes = imageResponse.bodyAsBytes()
231
// Save to file or process binary data
232
233
// Streaming large response
234
val largeResponse = client.get("https://example.com/large-file")
235
val channel = largeResponse.bodyAsChannel()
236
val outputFile = File("downloaded-file")
237
238
outputFile.outputStream().use { output ->
239
val buffer = ByteArray(8192)
240
while (!channel.isClosedForRead) {
241
val bytesRead = channel.readAvailable(buffer)
242
if (bytesRead > 0) {
243
output.write(buffer, 0, bytesRead)
244
}
245
}
246
}
247
```
248
249
### Response Status Handling
250
251
Utilities for handling HTTP status codes and response validation.
252
253
```kotlin { .api }
254
/**
255
* HTTP status code representation
256
*/
257
class HttpStatusCode(val value: Int, val description: String) {
258
/** Check if status indicates success (2xx) */
259
fun isSuccess(): Boolean = value in 200..299
260
261
/** Check if status indicates redirection (3xx) */
262
fun isRedirection(): Boolean = value in 300..399
263
264
/** Check if status indicates client error (4xx) */
265
fun isClientError(): Boolean = value in 400..499
266
267
/** Check if status indicates server error (5xx) */
268
fun isServerError(): Boolean = value in 500..599
269
270
companion object {
271
// Common status codes
272
val Continue = HttpStatusCode(100, "Continue")
273
val OK = HttpStatusCode(200, "OK")
274
val Created = HttpStatusCode(201, "Created")
275
val Accepted = HttpStatusCode(202, "Accepted")
276
val NoContent = HttpStatusCode(204, "No Content")
277
val MovedPermanently = HttpStatusCode(301, "Moved Permanently")
278
val Found = HttpStatusCode(302, "Found")
279
val NotModified = HttpStatusCode(304, "Not Modified")
280
val BadRequest = HttpStatusCode(400, "Bad Request")
281
val Unauthorized = HttpStatusCode(401, "Unauthorized")
282
val Forbidden = HttpStatusCode(403, "Forbidden")
283
val NotFound = HttpStatusCode(404, "Not Found")
284
val MethodNotAllowed = HttpStatusCode(405, "Method Not Allowed")
285
val NotAcceptable = HttpStatusCode(406, "Not Acceptable")
286
val Conflict = HttpStatusCode(409, "Conflict")
287
val UnprocessableEntity = HttpStatusCode(422, "Unprocessable Entity")
288
val InternalServerError = HttpStatusCode(500, "Internal Server Error")
289
val NotImplemented = HttpStatusCode(501, "Not Implemented")
290
val BadGateway = HttpStatusCode(502, "Bad Gateway")
291
val ServiceUnavailable = HttpStatusCode(503, "Service Unavailable")
292
val GatewayTimeout = HttpStatusCode(504, "Gateway Timeout")
293
}
294
}
295
```
296
297
**Usage Examples:**
298
299
```kotlin
300
import io.ktor.client.*
301
import io.ktor.client.request.*
302
import io.ktor.client.statement.*
303
import io.ktor.http.*
304
305
val client = HttpClient()
306
307
val response = client.get("https://api.example.com/resource")
308
309
// Status checking
310
when {
311
response.status.isSuccess() -> {
312
println("Success: ${response.bodyAsText()}")
313
}
314
response.status.isClientError() -> {
315
println("Client error: ${response.status.description}")
316
if (response.status == HttpStatusCode.Unauthorized) {
317
// Handle authentication
318
} else if (response.status == HttpStatusCode.NotFound) {
319
// Handle resource not found
320
}
321
}
322
response.status.isServerError() -> {
323
println("Server error: ${response.status.description}")
324
// Handle server errors, maybe retry
325
}
326
response.status.isRedirection() -> {
327
println("Redirection: ${response.headers["Location"]}")
328
}
329
}
330
331
// Specific status code handling
332
val createResponse = client.post("https://api.example.com/users") {
333
setBody(userData)
334
}
335
336
when (createResponse.status) {
337
HttpStatusCode.Created -> {
338
val newUser = createResponse.body<User>()
339
println("User created with ID: ${newUser.id}")
340
}
341
HttpStatusCode.BadRequest -> {
342
val error = createResponse.bodyAsText()
343
println("Validation error: $error")
344
}
345
HttpStatusCode.Conflict -> {
346
println("User already exists")
347
}
348
else -> {
349
println("Unexpected status: ${createResponse.status}")
350
}
351
}
352
```
353
354
### Response Headers Access
355
356
Utilities for accessing and processing response headers.
357
358
```kotlin { .api }
359
/**
360
* Response headers interface
361
*/
362
interface Headers : StringValues {
363
/** Get header value by name */
364
operator fun get(name: String): String?
365
366
/** Get all header values by name */
367
fun getAll(name: String): List<String>?
368
369
/** Check if header exists */
370
fun contains(name: String): Boolean
371
372
/** Check if header contains specific value */
373
fun contains(name: String, value: String): Boolean
374
375
/** Get header names */
376
fun names(): Set<String>
377
378
/** Check if headers are empty */
379
fun isEmpty(): Boolean
380
381
/** Iterate over all header entries */
382
fun entries(): Set<Map.Entry<String, List<String>>>
383
384
/** Convert to map */
385
fun toMap(): Map<String, List<String>>
386
}
387
```
388
389
**Usage Examples:**
390
391
```kotlin
392
import io.ktor.client.*
393
import io.ktor.client.request.*
394
import io.ktor.client.statement.*
395
import io.ktor.http.*
396
397
val client = HttpClient()
398
399
val response = client.get("https://api.example.com/data")
400
401
// Access specific headers
402
val contentType = response.headers["Content-Type"]
403
val contentLength = response.headers["Content-Length"]?.toLongOrNull()
404
val etag = response.headers["ETag"]
405
val lastModified = response.headers["Last-Modified"]
406
407
println("Content-Type: $contentType")
408
println("Content-Length: $contentLength")
409
println("ETag: $etag")
410
411
// Check for header existence
412
if (response.headers.contains("Cache-Control")) {
413
val cacheControl = response.headers["Cache-Control"]
414
println("Cache-Control: $cacheControl")
415
}
416
417
// Get all values for multi-value headers
418
val setCookies = response.headers.getAll("Set-Cookie")
419
setCookies?.forEach { cookie ->
420
println("Set-Cookie: $cookie")
421
}
422
423
// Iterate over all headers
424
response.headers.entries().forEach { (name, values) ->
425
values.forEach { value ->
426
println("$name: $value")
427
}
428
}
429
430
// Check content type
431
val contentType2 = response.headers["Content-Type"]
432
when {
433
contentType2?.contains("application/json") == true -> {
434
val jsonData = response.body<JsonObject>()
435
// Handle JSON response
436
}
437
contentType2?.contains("text/html") == true -> {
438
val htmlContent = response.bodyAsText()
439
// Handle HTML response
440
}
441
contentType2?.contains("image/") == true -> {
442
val imageBytes = response.bodyAsBytes()
443
// Handle image response
444
}
445
}
446
```
447
448
### Response Streaming and Processing
449
450
Advanced response processing for streaming, chunked, and large responses.
451
452
```kotlin { .api }
453
/**
454
* Process response in streaming fashion
455
* @param block Processing block receiving ByteReadChannel
456
*/
457
suspend fun <T> HttpResponse.bodyAsFlow(
458
block: suspend (ByteReadChannel) -> T
459
): T
460
461
/**
462
* Process response with custom channel handling
463
* @param block Channel processing block
464
*/
465
suspend fun <T> HttpStatement.execute(
466
block: suspend (HttpResponse) -> T
467
): T
468
```
469
470
**Usage Examples:**
471
472
```kotlin
473
import io.ktor.client.*
474
import io.ktor.client.statement.*
475
import io.ktor.utils.io.*
476
import java.io.File
477
478
val client = HttpClient()
479
480
// Download large file with progress
481
val downloadResponse = client.prepareGet("https://example.com/large-file.zip")
482
483
val outputFile = File("downloaded-file.zip")
484
var bytesDownloaded = 0L
485
val totalBytes = downloadResponse.execute { response ->
486
response.headers["Content-Length"]?.toLongOrNull()
487
}
488
489
downloadResponse.execute { response ->
490
val channel = response.bodyAsChannel()
491
492
outputFile.outputStream().use { output ->
493
val buffer = ByteArray(8192)
494
while (!channel.isClosedForRead) {
495
val bytesRead = channel.readAvailable(buffer)
496
if (bytesRead > 0) {
497
output.write(buffer, 0, bytesRead)
498
bytesDownloaded += bytesRead
499
500
// Progress reporting
501
totalBytes?.let { total ->
502
val progress = (bytesDownloaded * 100 / total).toInt()
503
println("Download progress: $progress%")
504
}
505
}
506
}
507
}
508
}
509
510
// Process JSON streaming response
511
val streamingResponse = client.prepareGet("https://api.example.com/stream")
512
513
streamingResponse.execute { response ->
514
val channel = response.bodyAsChannel()
515
val buffer = StringBuilder()
516
val tempBuffer = ByteArray(1024)
517
518
while (!channel.isClosedForRead) {
519
val bytesRead = channel.readAvailable(tempBuffer)
520
if (bytesRead > 0) {
521
val chunk = String(tempBuffer, 0, bytesRead, Charsets.UTF_8)
522
buffer.append(chunk)
523
524
// Process complete JSON objects
525
while (buffer.contains('\n')) {
526
val line = buffer.substring(0, buffer.indexOf('\n'))
527
buffer.delete(0, buffer.indexOf('\n') + 1)
528
529
if (line.isNotBlank()) {
530
// Process JSON line
531
processJsonLine(line)
532
}
533
}
534
}
535
}
536
}
537
```
538
539
## Types
540
541
### Response Types
542
543
```kotlin { .api }
544
class HttpResponseData(
545
val statusCode: HttpStatusCode,
546
val requestTime: GMTDate,
547
val headers: Headers,
548
val version: HttpProtocolVersion,
549
val body: Any,
550
val callContext: CoroutineContext
551
)
552
553
data class HttpResponseContainer(
554
val expectedType: TypeInfo,
555
val response: Any
556
)
557
558
enum class HttpProtocolVersion(val name: String, val major: Int, val minor: Int) {
559
HTTP_1_0("HTTP/1.0", 1, 0),
560
HTTP_1_1("HTTP/1.1", 1, 1),
561
HTTP_2_0("HTTP/2.0", 2, 0),
562
SPDY_3("SPDY/3", 3, 0),
563
QUIC("QUIC", 1, 0)
564
}
565
566
data class GMTDate(
567
val timestamp: Long,
568
val seconds: Int,
569
val minutes: Int,
570
val hours: Int,
571
val dayOfMonth: Int,
572
val month: Month,
573
val year: Int,
574
val dayOfWeek: Weekday,
575
val dayOfYear: Int
576
)
577
```
578
579
### Exception Types
580
581
```kotlin { .api }
582
class DoubleReceiveException(call: HttpClientCall) :
583
IllegalStateException("Response already received: $call")
584
585
class NoTransformationFoundException(val from: KType, val to: KType) :
586
UnsupportedOperationException("No transformation found: $from -> $to")
587
588
class ResponseException(
589
val response: HttpResponse,
590
val cachedResponseText: String
591
) : IllegalStateException("Bad response: ${response.status}")
592
593
class RedirectResponseException(
594
response: HttpResponse,
595
cachedResponseText: String
596
) : ResponseException(response, cachedResponseText)
597
598
class ClientRequestException(
599
response: HttpResponse,
600
cachedResponseText: String
601
) : ResponseException(response, cachedResponseText)
602
603
class ServerResponseException(
604
response: HttpResponse,
605
cachedResponseText: String
606
) : ResponseException(response, cachedResponseText)
607
```