or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

built-in-plugins.mdengine-configuration.mdform-data-content.mdhttp-client.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.md

response-handling.mddocs/

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

```