or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdconfiguration.mdengine.mdindex.mdrequest-response.mdrouting.md

request-response.mddocs/

0

# Request and Response Handling

1

2

Ktor provides comprehensive request and response handling capabilities through the ApplicationRequest and ApplicationResponse interfaces, along with powerful pipeline-based content transformation and type-safe serialization support.

3

4

## ApplicationRequest Interface

5

6

### Core Request Interface

7

8

```kotlin { .api }

9

interface ApplicationRequest {

10

val local: RequestConnectionPoint

11

val pipeline: ApplicationReceivePipeline

12

val queryParameters: Parameters

13

val headers: Headers

14

val cookies: RequestCookies

15

}

16

```

17

18

### Request Properties

19

20

```kotlin { .api }

21

// Request URI and path information

22

val ApplicationRequest.uri: String // Full URI including query string

23

val ApplicationRequest.path: String // URL path without query string

24

val ApplicationRequest.document: String // Document name from path

25

val ApplicationRequest.queryString: String // Query string

26

27

// HTTP method and version

28

val ApplicationRequest.httpMethod: HttpMethod // HTTP method (possibly overridden)

29

val ApplicationRequest.httpVersion: String // HTTP version string

30

31

// Content information

32

fun ApplicationRequest.contentType(): ContentType? // Request content type

33

fun ApplicationRequest.contentLength(): Long? // Content-Length header value

34

fun ApplicationRequest.contentCharset(): Charset? // Request charset

35

```

36

37

### Header Access Functions

38

39

```kotlin { .api }

40

// Header access functions

41

fun ApplicationRequest.header(name: String): String? // Get header value by name

42

43

// Standard header accessors

44

fun ApplicationRequest.authorization(): String? // Authorization header

45

fun ApplicationRequest.location(): String? // Location header

46

fun ApplicationRequest.accept(): String? // Accept header

47

fun ApplicationRequest.acceptEncoding(): String? // Accept-Encoding header

48

fun ApplicationRequest.acceptLanguage(): String? // Accept-Language header

49

fun ApplicationRequest.acceptCharset(): String? // Accept-Charset header

50

fun ApplicationRequest.userAgent(): String? // User-Agent header

51

fun ApplicationRequest.cacheControl(): String? // Cache-Control header

52

53

// Host and port information

54

fun ApplicationRequest.host(): String // Host without port

55

fun ApplicationRequest.port(): Int // Port number (default: 80 for HTTP, 443 for HTTPS)

56

```

57

58

### Parsed Header Content

59

60

```kotlin { .api }

61

// Parsed Accept header items

62

fun ApplicationRequest.acceptItems(): List<HeaderValue> // Parsed Accept header content types

63

fun ApplicationRequest.acceptEncodingItems(): List<HeaderValue> // Parsed Accept-Encoding items

64

fun ApplicationRequest.acceptLanguageItems(): List<HeaderValue> // Parsed Accept-Language items

65

fun ApplicationRequest.acceptCharsetItems(): List<HeaderValue> // Parsed Accept-Charset items

66

67

// Range header parsing

68

fun ApplicationRequest.ranges(): RangesSpecifier? // Parsed Range header

69

70

// Example usage

71

get("/content") {

72

val acceptItems = call.request.acceptItems()

73

val preferredType = acceptItems.firstOrNull()?.value

74

75

when (preferredType) {

76

"application/json" -> call.respond(jsonData)

77

"application/xml" -> call.respondText(xmlData, ContentType.Application.Xml)

78

else -> call.respondText("Unsupported content type", status = HttpStatusCode.NotAcceptable)

79

}

80

}

81

```

82

83

### Content Type Detection

84

85

```kotlin { .api }

86

// Content type detection functions

87

fun ApplicationRequest.isChunked(): Boolean // Check if request is chunk-encoded

88

fun ApplicationRequest.isMultipart(): Boolean // Check if request is multipart

89

90

// Usage example

91

post("/upload") {

92

if (call.request.isMultipart()) {

93

// Handle multipart form data

94

val multipart = call.receiveMultipart()

95

} else {

96

// Handle regular content

97

val content = call.receive<String>()

98

}

99

}

100

```

101

102

## Content Receiving

103

104

### Receive Functions

105

106

```kotlin { .api }

107

// Receive typed content from request body

108

suspend fun <T : Any> ApplicationCall.receive(): T

109

suspend fun <T : Any> ApplicationCall.receive(typeInfo: TypeInfo): T

110

suspend fun <T : Any> ApplicationCall.receiveNullable(): T?

111

suspend fun <T : Any> ApplicationCall.receiveNullable(typeInfo: TypeInfo): T?

112

113

// Examples

114

post("/users") {

115

// Receive JSON as data class

116

val user = call.receive<User>()

117

val created = userService.create(user)

118

call.respond(HttpStatusCode.Created, created)

119

}

120

121

post("/data") {

122

// Receive nullable content

123

val data = call.receiveNullable<RequestData>()

124

if (data != null) {

125

processData(data)

126

call.respond(HttpStatusCode.OK)

127

} else {

128

call.respond(HttpStatusCode.BadRequest, "Invalid data")

129

}

130

}

131

```

132

133

### Receive Pipeline

134

135

```kotlin { .api }

136

// Application receive pipeline for content transformation

137

class ApplicationReceivePipeline : Pipeline<Any, ApplicationCall> {

138

companion object {

139

val Before = PipelinePhase("Before")

140

val Transform = PipelinePhase("Transform")

141

val After = PipelinePhase("After")

142

}

143

}

144

145

// Pipeline request implementation

146

interface PipelineRequest : ApplicationRequest {

147

// Pipeline-specific request functionality

148

}

149

```

150

151

### Content Type Examples

152

153

```kotlin { .api }

154

routing {

155

// Receive different content types

156

post("/json") {

157

val data = call.receive<JsonData>()

158

call.respond(processJsonData(data))

159

}

160

161

post("/form") {

162

val parameters = call.receiveParameters()

163

val name = parameters["name"] ?: ""

164

val email = parameters["email"] ?: ""

165

call.respondText("Received: $name, $email")

166

}

167

168

post("/text") {

169

val text = call.receiveText()

170

call.respondText("Echo: $text")

171

}

172

173

post("/bytes") {

174

val bytes = call.receive<ByteArray>()

175

call.respondBytes(processBytes(bytes))

176

}

177

}

178

```

179

180

## ApplicationResponse Interface

181

182

### Core Response Interface

183

184

```kotlin { .api }

185

interface ApplicationResponse {

186

val status: HttpStatusCode?

187

val headers: ResponseHeaders

188

val cookies: ResponseCookies

189

val pipeline: ApplicationSendPipeline

190

val isCommitted: Boolean

191

}

192

```

193

194

### Response Functions

195

196

```kotlin { .api }

197

// Send typed response

198

suspend fun ApplicationCall.respond(message: Any)

199

suspend fun ApplicationCall.respond(status: HttpStatusCode, message: Any)

200

suspend fun ApplicationCall.respond(message: Any, typeInfo: TypeInfo)

201

suspend fun ApplicationCall.respondNullable(message: Any?)

202

203

// Send text response

204

suspend fun ApplicationCall.respondText(

205

text: String,

206

contentType: ContentType? = null,

207

status: HttpStatusCode? = null

208

)

209

210

// Send byte array response

211

suspend fun ApplicationCall.respondBytes(

212

bytes: ByteArray,

213

contentType: ContentType? = null,

214

status: HttpStatusCode? = null

215

)

216

217

// Send from kotlinx-io Source

218

suspend fun ApplicationCall.respondSource(

219

source: Source,

220

contentType: ContentType? = null,

221

status: HttpStatusCode? = null,

222

contentLength: Long? = null

223

)

224

225

// Send with producer function

226

suspend fun ApplicationCall.respondBytesWriter(

227

contentType: ContentType? = null,

228

status: HttpStatusCode? = null,

229

contentLength: Long? = null,

230

producer: suspend ByteWriteChannel.() -> Unit

231

)

232

```

233

234

### Redirect Responses

235

236

```kotlin { .api }

237

// Send redirect response

238

suspend fun ApplicationCall.respondRedirect(url: String, permanent: Boolean = false)

239

240

// Examples

241

get("/old-path") {

242

call.respondRedirect("/new-path", permanent = true)

243

}

244

245

post("/login") {

246

val credentials = call.receive<LoginRequest>()

247

if (authenticate(credentials)) {

248

call.respondRedirect("/dashboard")

249

} else {

250

call.respondRedirect("/login?error=invalid")

251

}

252

}

253

```

254

255

### Response Headers and Cookies

256

257

```kotlin { .api }

258

// Response headers

259

interface ResponseHeaders : Headers {

260

operator fun set(name: String, value: String)

261

fun append(name: String, value: String)

262

fun remove(name: String)

263

}

264

265

// Response cookies

266

interface ResponseCookies {

267

operator fun set(name: String, value: String)

268

fun append(name: String, value: String, vararg items: CookieEncoding)

269

fun appendExpired(name: String)

270

}

271

272

// Usage examples

273

get("/set-headers") {

274

call.response.headers.append("X-Custom-Header", "custom-value")

275

call.response.headers["Cache-Control"] = "no-cache"

276

call.respondText("Headers set")

277

}

278

279

get("/set-cookies") {

280

call.response.cookies.append("session-id", "abc123")

281

call.response.cookies.append("theme", "dark", CookieEncoding.URI_ENCODING)

282

call.respondText("Cookies set")

283

}

284

```

285

286

## Response Pipeline

287

288

### ApplicationSendPipeline

289

290

```kotlin { .api }

291

// Application send pipeline for response transformation

292

class ApplicationSendPipeline : Pipeline<Any, ApplicationCall> {

293

companion object {

294

val Before = PipelinePhase("Before")

295

val Transform = PipelinePhase("Transform")

296

val Render = PipelinePhase("Render")

297

val ContentEncoding = PipelinePhase("ContentEncoding")

298

val TransferEncoding = PipelinePhase("TransferEncoding")

299

val After = PipelinePhase("After")

300

}

301

}

302

303

// Pipeline response implementation

304

interface PipelineResponse : ApplicationResponse {

305

// Pipeline-specific response functionality

306

}

307

```

308

309

### Response Content Types

310

311

```kotlin { .api }

312

// Response type information

313

class ResponseType(

314

val jvmErasure: KClass<*>,

315

val reifiedType: Type

316

)

317

318

// Content type utilities

319

fun ApplicationResponse.defaultTextContentType(charset: Charset? = null): ContentType

320

```

321

322

## HTTP/2 Push Support

323

324

### ResponsePushBuilder

325

326

```kotlin { .api }

327

// HTTP/2 push promise builder

328

interface ResponsePushBuilder {

329

var method: HttpMethod

330

var url: URLBuilder

331

val headers: HeadersBuilder

332

val versions: MutableList<Version>

333

}

334

335

// Default push builder implementation

336

class DefaultResponsePushBuilder(

337

call: ApplicationCall

338

) : ResponsePushBuilder {

339

override var method: HttpMethod = HttpMethod.Get

340

override val url: URLBuilder = URLBuilder()

341

override val headers: HeadersBuilder = HeadersBuilder()

342

override val versions: MutableList<Version> = mutableListOf()

343

}

344

345

// HTTP/2 push attribute

346

object UseHttp2Push : AttributeKey<ResponsePushBuilder>

347

```

348

349

### Push Promise Example

350

351

```kotlin { .api }

352

get("/page") {

353

// Send push promises for resources

354

val pushBuilder = DefaultResponsePushBuilder(call)

355

pushBuilder.url.path("/styles.css")

356

call.attributes.put(UseHttp2Push, pushBuilder)

357

358

// Send main response

359

call.respondText(generateHtmlPage(), ContentType.Text.Html)

360

}

361

```

362

363

## Request Connection Point

364

365

### Connection Information

366

367

```kotlin { .api }

368

// Connection point information (immutable)

369

interface RequestConnectionPoint {

370

val scheme: String // "http" or "https"

371

val version: String // HTTP version

372

val port: Int // Port number

373

val host: String // Host name

374

val uri: String // Request URI

375

val method: HttpMethod // HTTP method

376

}

377

378

// Mutable connection point for modifications

379

class MutableOriginConnectionPoint : RequestConnectionPoint {

380

override var scheme: String

381

override var version: String

382

override var port: Int

383

override var host: String

384

override var uri: String

385

override var method: HttpMethod

386

}

387

388

// Access connection information

389

get("/info") {

390

val local = call.request.local

391

call.respond(mapOf(

392

"scheme" to local.scheme,

393

"host" to local.host,

394

"port" to local.port,

395

"method" to local.method.value,

396

"uri" to local.uri

397

))

398

}

399

```

400

401

### Origin Extensions

402

403

```kotlin { .api }

404

// Access origin connection point

405

val ApplicationRequest.origin: RequestConnectionPoint

406

407

// Access mutable origin for proxies/load balancers

408

val ApplicationRequest.mutableOriginConnectionPoint: MutableOriginConnectionPoint

409

410

// Example: Handle X-Forwarded headers

411

intercept(ApplicationCallPipeline.Setup) {

412

val forwarded = call.request.header("X-Forwarded-For")

413

if (forwarded != null) {

414

call.request.mutableOriginConnectionPoint.host = forwarded

415

}

416

417

val forwardedProto = call.request.header("X-Forwarded-Proto")

418

if (forwardedProto != null) {

419

call.request.mutableOriginConnectionPoint.scheme = forwardedProto

420

}

421

}

422

```

423

424

## Exception Handling

425

426

### Request/Response Exceptions

427

428

```kotlin { .api }

429

// HTTP 404 exception

430

class NotFoundException(message: String? = "Not Found") : Exception(message)

431

432

// Missing parameter exception

433

class MissingRequestParameterException(parameterName: String) : Exception(

434

"Required parameter $parameterName is missing"

435

)

436

437

// Parameter conversion exception

438

class ParameterConversionException(

439

parameterName: String,

440

type: String,

441

cause: Throwable? = null

442

) : Exception("Parameter $parameterName cannot be converted to $type", cause)

443

444

// Content transformation exception

445

class CannotTransformContentToTypeException(type: TypeInfo) : Exception(

446

"Cannot transform content to ${type.type}"

447

)

448

449

// Unsupported media type exception

450

class UnsupportedMediaTypeException(contentType: ContentType) : Exception(

451

"Content type $contentType is not supported"

452

)

453

454

// Payload too large exception

455

class PayloadTooLargeException(message: String) : Exception(message)

456

```

457

458

### Exception Handling Examples

459

460

```kotlin { .api }

461

routing {

462

get("/users/{id}") {

463

try {

464

val id = call.parameters["id"]

465

?: throw MissingRequestParameterException("id")

466

467

val userId = id.toLongOrNull()

468

?: throw ParameterConversionException("id", "Long")

469

470

val user = userService.findById(userId)

471

?: throw NotFoundException("User with id $userId not found")

472

473

call.respond(user)

474

} catch (e: MissingRequestParameterException) {

475

call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))

476

} catch (e: ParameterConversionException) {

477

call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))

478

} catch (e: NotFoundException) {

479

call.respond(HttpStatusCode.NotFound, mapOf("error" to e.message))

480

}

481

}

482

483

post("/upload") {

484

try {

485

if (!call.request.isMultipart()) {

486

throw UnsupportedMediaTypeException(call.request.contentType()!!)

487

}

488

489

val multipart = call.receiveMultipart()

490

// Process multipart data

491

492

} catch (e: UnsupportedMediaTypeException) {

493

call.respond(HttpStatusCode.UnsupportedMediaType, mapOf("error" to e.message))

494

} catch (e: PayloadTooLargeException) {

495

call.respond(HttpStatusCode.PayloadTooLarge, mapOf("error" to e.message))

496

}

497

}

498

}

499

```

500

501

## Advanced Request/Response Patterns

502

503

### Content Negotiation

504

505

```kotlin { .api }

506

get("/api/data") {

507

val acceptHeader = call.request.accept()

508

val data = dataService.getData()

509

510

when {

511

acceptHeader?.contains("application/json") == true -> {

512

call.respond(data)

513

}

514

acceptHeader?.contains("application/xml") == true -> {

515

call.respondText(data.toXml(), ContentType.Application.Xml)

516

}

517

acceptHeader?.contains("text/csv") == true -> {

518

call.respondText(data.toCsv(), ContentType.Text.CSV)

519

}

520

else -> {

521

call.respond(HttpStatusCode.NotAcceptable)

522

}

523

}

524

}

525

```

526

527

### Conditional Responses

528

529

```kotlin { .api }

530

get("/files/{filename}") {

531

val filename = call.parameters["filename"]!!

532

val file = File("uploads/$filename")

533

534

if (!file.exists()) {

535

call.respond(HttpStatusCode.NotFound)

536

return@get

537

}

538

539

// Handle If-Modified-Since

540

val ifModifiedSince = call.request.header("If-Modified-Since")

541

if (ifModifiedSince != null) {

542

val modifiedDate = parseHttpDate(ifModifiedSince)

543

if (file.lastModified() <= modifiedDate.toEpochMilli()) {

544

call.respond(HttpStatusCode.NotModified)

545

return@get

546

}

547

}

548

549

// Handle Range requests

550

val ranges = call.request.ranges()

551

if (ranges != null) {

552

// Handle partial content

553

call.response.status(HttpStatusCode.PartialContent)

554

// Implement range serving

555

} else {

556

call.respondFile(file)

557

}

558

}

559

```

560

561

### Streaming Responses

562

563

```kotlin { .api }

564

get("/stream") {

565

call.respondBytesWriter(ContentType.Text.Plain) {

566

repeat(1000) { i ->

567

writeStringUtf8("Line $i\n")

568

flush()

569

delay(10) // Simulate slow data generation

570

}

571

}

572

}

573

574

get("/download") {

575

val file = File("large-file.dat")

576

call.response.header("Content-Disposition", "attachment; filename=\"${file.name}\"")

577

call.respondFile(file)

578

}

579

```

580

581

## Complete Request/Response Example

582

583

```kotlin { .api }

584

fun Application.configureRequestResponse() {

585

routing {

586

// Handle different content types

587

post("/api/users") {

588

try {

589

// Validate content type

590

val contentType = call.request.contentType()

591

if (contentType?.match(ContentType.Application.Json) != true) {

592

call.respond(

593

HttpStatusCode.UnsupportedMediaType,

594

"Expected application/json"

595

)

596

return@post

597

}

598

599

// Receive and validate user data

600

val createRequest = call.receiveNullable<CreateUserRequest>()

601

if (createRequest == null) {

602

call.respond(HttpStatusCode.BadRequest, "Invalid user data")

603

return@post

604

}

605

606

// Create user

607

val user = userService.createUser(createRequest)

608

609

// Set response headers

610

call.response.headers["Location"] = "/api/users/${user.id}"

611

call.response.headers["X-Created-At"] = Instant.now().toString()

612

613

// Send response

614

call.respond(HttpStatusCode.Created, user)

615

616

} catch (e: CannotTransformContentToTypeException) {

617

call.respond(HttpStatusCode.BadRequest, "Invalid JSON format")

618

} catch (e: Exception) {

619

application.log.error("Error creating user", e)

620

call.respond(HttpStatusCode.InternalServerError, "Internal server error")

621

}

622

}

623

624

// File upload with progress

625

post("/upload") {

626

val contentLength = call.request.contentLength()

627

if (contentLength != null && contentLength > 10_000_000) {

628

call.respond(HttpStatusCode.PayloadTooLarge, "File too large")

629

return@post

630

}

631

632

if (!call.request.isMultipart()) {

633

call.respond(HttpStatusCode.BadRequest, "Multipart content expected")

634

return@post

635

}

636

637

val multipart = call.receiveMultipart()

638

val uploadedFiles = mutableListOf<String>()

639

640

multipart.forEachPart { part ->

641

when (part) {

642

is PartData.FileItem -> {

643

val fileName = part.originalFileName ?: "unknown"

644

val file = File("uploads/$fileName")

645

part.streamProvider().use { input ->

646

file.outputStream().use { output ->

647

input.copyTo(output)

648

}

649

}

650

uploadedFiles.add(fileName)

651

}

652

else -> part.dispose()

653

}

654

}

655

656

call.respond(mapOf(

657

"message" to "Upload successful",

658

"files" to uploadedFiles

659

))

660

}

661

}

662

}

663

664

data class CreateUserRequest(

665

val name: String,

666

val email: String,

667

val age: Int?

668

)

669

```

670

671

This comprehensive documentation covers all aspects of Ktor's request and response handling system, from basic content receiving and sending to advanced patterns like streaming and content negotiation.