0
# Response Handling
1
2
Comprehensive response handling with typed body access, streaming capabilities, and flexible content processing for HTTP responses.
3
4
## Capabilities
5
6
### HttpResponse Class
7
8
Abstract base class representing HTTP responses with access to status, headers, and content.
9
10
```kotlin { .api }
11
/**
12
* Abstract HTTP response class implementing HttpMessage and CoroutineScope
13
*/
14
abstract class HttpResponse : HttpMessage, CoroutineScope {
15
/** Associated client call */
16
abstract val call: HttpClientCall
17
18
/** Response status code */
19
abstract val status: HttpStatusCode
20
21
/** HTTP protocol version */
22
abstract val version: HttpProtocolVersion
23
24
/** Request start time */
25
abstract val requestTime: GMTDate
26
27
/** Response start time */
28
abstract val responseTime: GMTDate
29
30
/** Raw response content as byte channel */
31
abstract val content: ByteReadChannel
32
33
/** Coroutine context inherited from call */
34
override val coroutineContext: CoroutineContext
35
}
36
37
/**
38
* Get the associated request for this response
39
*/
40
val HttpResponse.request: HttpRequest
41
```
42
43
**Usage Examples:**
44
45
```kotlin
46
import io.ktor.client.*
47
import io.ktor.client.statement.*
48
import io.ktor.http.*
49
50
val client = HttpClient()
51
val response: HttpResponse = client.get("https://api.example.com/users")
52
53
// Access response properties
54
println("Status: ${response.status}")
55
println("Status Code: ${response.status.value}")
56
println("HTTP Version: ${response.version}")
57
println("Request Time: ${response.requestTime}")
58
println("Response Time: ${response.responseTime}")
59
60
// Access headers
61
println("Content-Type: ${response.headers[HttpHeaders.ContentType]}")
62
println("Content-Length: ${response.headers[HttpHeaders.ContentLength]}")
63
64
// Access request information
65
println("Request URL: ${response.request.url}")
66
println("Request Method: ${response.request.method}")
67
```
68
69
### Response Body Access
70
71
Functions for accessing response body content in various formats with type safety.
72
73
```kotlin { .api }
74
/**
75
* Get response body as text with optional charset fallback
76
*/
77
suspend fun HttpResponse.bodyAsText(fallbackCharset: Charset = Charsets.UTF_8): String
78
79
/**
80
* Get response body as byte channel for streaming
81
*/
82
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel
83
84
/**
85
* Get typed response body using reified generics
86
*/
87
suspend inline fun <reified T> HttpResponse.body(): T
88
89
/**
90
* Get typed response body with explicit type information
91
*/
92
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T
93
```
94
95
**Usage Examples:**
96
97
```kotlin
98
val response = client.get("https://api.example.com/users")
99
100
// Get as text
101
val textContent: String = response.bodyAsText()
102
println("Response: $textContent")
103
104
// Get as text with specific charset
105
val utf8Content = response.bodyAsText(Charsets.UTF_8)
106
107
// Get as typed object (requires content negotiation plugin)
108
data class User(val id: Int, val name: String, val email: String)
109
val user: User = response.body()
110
111
// Get as list of objects
112
val users: List<User> = response.body()
113
114
// Get as byte array
115
val bytes: ByteArray = response.body()
116
117
// Streaming content
118
val channel: ByteReadChannel = response.bodyAsChannel()
119
// Process channel data as needed
120
```
121
122
### HttpStatement Class
123
124
Reusable prepared request that can be executed multiple times with different response handling.
125
126
```kotlin { .api }
127
/**
128
* Reusable prepared HTTP request
129
*/
130
class HttpStatement(
131
private val builder: HttpRequestBuilder,
132
private val client: HttpClient
133
) {
134
/**
135
* Execute request with custom response handler
136
*/
137
suspend fun <T> execute(block: suspend (response: HttpResponse) -> T): T
138
139
/**
140
* Execute request and get downloaded response
141
*/
142
suspend fun execute(): HttpResponse
143
144
/**
145
* Execute request and get typed response body
146
*/
147
suspend inline fun <reified T> body(): T
148
149
/**
150
* Execute request with typed response handler
151
*/
152
suspend inline fun <reified T, R> body(
153
crossinline block: suspend (response: T) -> R
154
): R
155
}
156
```
157
158
**Usage Examples:**
159
160
```kotlin
161
// Create prepared statement
162
val statement = client.prepareGet("https://api.example.com/users/{id}")
163
164
// Execute with custom handler
165
val userInfo = statement.execute { response ->
166
when (response.status) {
167
HttpStatusCode.OK -> response.bodyAsText()
168
HttpStatusCode.NotFound -> "User not found"
169
else -> "Error: ${response.status}"
170
}
171
}
172
173
// Execute and get response
174
val response = statement.execute()
175
val content = response.bodyAsText()
176
177
// Execute and get typed body
178
val user: User = statement.body()
179
180
// Execute with typed handler
181
val userName = statement.body<User> { user ->
182
user.name
183
}
184
185
// Reuse statement multiple times
186
repeat(5) { id ->
187
val user = statement.body<User>()
188
println("User $id: ${user.name}")
189
}
190
```
191
192
### HttpClientCall Class
193
194
Request/response pair representing a complete HTTP exchange with attribute support.
195
196
```kotlin { .api }
197
/**
198
* HTTP client call representing request/response pair
199
*/
200
class HttpClientCall(
201
val client: HttpClient
202
) : CoroutineScope {
203
/** Call attributes inherited from request */
204
val attributes: Attributes
205
206
/** Request part of the call */
207
val request: HttpRequest
208
209
/** Response part of the call */
210
val response: HttpResponse
211
212
/** Coroutine context from response */
213
override val coroutineContext: CoroutineContext
214
215
/**
216
* Get typed body from response
217
*/
218
suspend inline fun <reified T> body(): T
219
220
/**
221
* Get typed body with explicit type information
222
*/
223
suspend fun <T> body(info: TypeInfo): T
224
225
/**
226
* Get nullable typed body
227
*/
228
suspend fun bodyNullable(info: TypeInfo): Any?
229
}
230
```
231
232
**Usage Examples:**
233
234
```kotlin
235
val response = client.get("https://api.example.com/users/123")
236
val call = response.call
237
238
// Access request/response through call
239
println("Request URL: ${call.request.url}")
240
println("Response Status: ${call.response.status}")
241
242
// Access client
243
val sameClient = call.client
244
245
// Get typed body through call
246
val user: User = call.body()
247
248
// Access call attributes
249
val requestId = call.attributes[AttributeKey<String>("RequestId")]
250
```
251
252
### Response Body Extensions
253
254
Additional convenience functions for response body access.
255
256
```kotlin { .api }
257
/**
258
* Get typed response body using reified generics
259
*/
260
suspend inline fun <reified T> HttpResponse.body(): T
261
262
/**
263
* Get typed response body with explicit type information
264
*/
265
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T
266
267
/**
268
* Get response body as byte array
269
*/
270
suspend fun HttpResponse.bodyAsBytes(): ByteArray
271
272
/**
273
* Save response body to file (JVM only)
274
*/
275
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel
276
```
277
278
**Usage Examples:**
279
280
```kotlin
281
val response = client.get("https://api.example.com/data.json")
282
283
// Different ways to access body
284
val jsonString: String = response.body()
285
val jsonBytes: ByteArray = response.bodyAsBytes()
286
val userData: UserData = response.body()
287
288
// Stream processing
289
val channel = response.bodyAsChannel()
290
val buffer = ByteArray(8192)
291
while (!channel.isClosedForRead) {
292
val bytesRead = channel.readAvailable(buffer)
293
if (bytesRead > 0) {
294
// Process buffer data
295
processChunk(buffer, bytesRead)
296
}
297
}
298
```
299
300
### Error Handling
301
302
Exception classes and patterns for handling response errors.
303
304
```kotlin { .api }
305
/**
306
* Thrown when attempting to receive response body twice
307
*/
308
class DoubleReceiveException(message: String) : IllegalStateException(message)
309
310
/**
311
* Thrown when response pipeline fails
312
*/
313
class ReceivePipelineException(
314
message: String,
315
cause: Throwable
316
) : IllegalStateException(message, cause)
317
318
/**
319
* Thrown when no suitable content transformation is found
320
*/
321
class NoTransformationFoundException(
322
from: KType,
323
to: KType
324
) : UnsupportedOperationException()
325
```
326
327
**Usage Examples:**
328
329
```kotlin
330
try {
331
val response = client.get("https://api.example.com/users")
332
val content1 = response.bodyAsText()
333
334
// This will throw DoubleReceiveException
335
val content2 = response.bodyAsText()
336
} catch (e: DoubleReceiveException) {
337
println("Cannot receive response body twice: ${e.message}")
338
}
339
340
try {
341
val response = client.get("https://api.example.com/data")
342
val customObject: MyCustomType = response.body()
343
} catch (e: NoTransformationFoundException) {
344
println("No transformer available for type: ${e.message}")
345
}
346
347
// Proper error handling pattern
348
val response = client.get("https://api.example.com/users")
349
when (response.status) {
350
HttpStatusCode.OK -> {
351
val users: List<User> = response.body()
352
// Handle success
353
}
354
HttpStatusCode.NotFound -> {
355
// Handle not found
356
}
357
in HttpStatusCode.InternalServerError..HttpStatusCode.fromValue(599) -> {
358
// Handle server errors
359
val errorBody = response.bodyAsText()
360
println("Server error: $errorBody")
361
}
362
else -> {
363
// Handle other status codes
364
println("Unexpected status: ${response.status}")
365
}
366
}
367
```
368
369
### Streaming Response Handling
370
371
Advanced streaming capabilities for large responses or real-time data.
372
373
```kotlin { .api }
374
/**
375
* Process response as stream without loading entire content into memory
376
*/
377
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel
378
```
379
380
**Usage Examples:**
381
382
```kotlin
383
// Download large file
384
val response = client.get("https://example.com/large-file.zip")
385
val channel = response.bodyAsChannel()
386
387
// Stream to file
388
val file = File("downloaded-file.zip")
389
file.outputStream().use { output ->
390
val buffer = ByteArray(8192)
391
while (!channel.isClosedForRead) {
392
val bytesRead = channel.readAvailable(buffer)
393
if (bytesRead > 0) {
394
output.write(buffer, 0, bytesRead)
395
}
396
}
397
}
398
399
// Process streaming JSON
400
val streamResponse = client.get("https://api.example.com/stream")
401
val streamChannel = streamResponse.bodyAsChannel()
402
403
// Read line by line (for text streams)
404
val reader = streamChannel.toInputStream().bufferedReader()
405
reader.useLines { lines ->
406
lines.forEach { line ->
407
// Process each line as it arrives
408
processJsonLine(line)
409
}
410
}
411
```
412
413
### Response Validation
414
415
Built-in response validation patterns and custom validation.
416
417
```kotlin { .api }
418
/**
419
* Response validation is handled by HttpCallValidator plugin
420
* Default validation can be configured in client setup
421
*/
422
```
423
424
**Usage Examples:**
425
426
```kotlin
427
import io.ktor.client.plugins.*
428
429
// Client with validation
430
val client = HttpClient {
431
expectSuccess = true // Throw exception for non-success status codes
432
433
HttpResponseValidator {
434
validateResponse { response ->
435
// Custom validation logic
436
if (response.status.value in 400..499) {
437
throw ClientRequestException(response, response.bodyAsText())
438
}
439
}
440
441
handleResponseExceptionWithRequest { exception, request ->
442
// Custom exception handling
443
when (exception) {
444
is ClientRequestException -> {
445
println("Client error for ${request.url}: ${exception.message}")
446
}
447
is ServerResponseException -> {
448
println("Server error for ${request.url}: ${exception.message}")
449
}
450
}
451
throw exception
452
}
453
}
454
}
455
456
// Usage with validation
457
try {
458
val response = client.get("https://api.example.com/users/999")
459
val user: User = response.body()
460
} catch (e: ClientRequestException) {
461
// Handle 4xx errors
462
println("Client error: ${e.response.status}")
463
} catch (e: ServerResponseException) {
464
// Handle 5xx errors
465
println("Server error: ${e.response.status}")
466
}
467
```