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.