or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-configuration.mdcookie-management.mdengine-configuration.mdform-handling.mdhttp-caching.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.mdwebsocket-support.md

response-handling.mddocs/

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

```