0
# HTTP Utilities and Extensions
1
2
HTTP-specific utilities, link headers, server push support, logging capabilities, and various helper functions for enhanced server functionality.
3
4
## Capabilities
5
6
### HTTP Link Headers
7
8
Support for HTTP Link headers as defined in RFC 8288.
9
10
```kotlin { .api }
11
/**
12
* Represents HTTP Link header information
13
*/
14
data class Link(
15
/** URI reference for the link */
16
val uri: String,
17
18
/** Relation types for this link */
19
val rel: List<String>,
20
21
/** Content type of the linked resource */
22
val type: ContentType? = null,
23
24
/** Human-readable title for the link */
25
val title: String? = null
26
)
27
28
/**
29
* Add Link header to response
30
*/
31
fun ApplicationResponse.linkHeader(link: Link)
32
33
/**
34
* Add Link header to response with simplified parameters
35
*/
36
fun ApplicationResponse.linkHeader(
37
uri: String,
38
rel: String,
39
type: ContentType? = null,
40
title: String? = null
41
)
42
```
43
44
### HTTP/2 Server Push
45
46
Support for HTTP/2 server push functionality.
47
48
```kotlin { .api }
49
/**
50
* Initiate HTTP/2 server push
51
*/
52
suspend fun ApplicationCall.push(block: ResponsePushBuilder.() -> Unit)
53
54
/**
55
* Builder for HTTP/2 push promises
56
*/
57
class ResponsePushBuilder {
58
/** URL to push */
59
var url: Url
60
61
/** HTTP method for pushed request */
62
var method: HttpMethod
63
64
/** Headers for pushed request */
65
val headers: HeadersBuilder
66
}
67
```
68
69
### Content Transformation
70
71
Default content transformations for request and response processing.
72
73
```kotlin { .api }
74
/**
75
* Configure default content transformations
76
*/
77
fun ApplicationSendPipeline.defaultTransformations()
78
79
/**
80
* Configure default content transformations for receive pipeline
81
*/
82
fun ApplicationReceivePipeline.defaultTransformations()
83
```
84
85
### HTTP Parsing Utilities
86
87
Utilities for parsing HTTP headers and cookies.
88
89
```kotlin { .api }
90
/**
91
* Parse Cookie header into individual cookies
92
*/
93
fun parseClientCookiesHeader(cookiesHeader: String): Map<String, String>
94
95
/**
96
* Parse Set-Cookie header
97
*/
98
fun parseServerSetCookieHeader(setCookieHeader: String): Cookie
99
```
100
101
### Logging and MDC Support
102
103
Mapped Diagnostic Context (MDC) support for structured logging.
104
105
```kotlin { .api }
106
/**
107
* Provides MDC (Mapped Diagnostic Context) functionality
108
*/
109
interface MDCProvider {
110
/** Get MDC value by key */
111
fun get(key: String): String?
112
113
/** Set MDC value */
114
fun put(key: String, value: String?)
115
116
/** Remove MDC value */
117
fun remove(key: String)
118
119
/** Clear all MDC values */
120
fun clear()
121
122
/** Get copy of current MDC context */
123
fun getCopyOfContextMap(): Map<String, String>?
124
125
/** Set entire MDC context */
126
fun setContextMap(contextMap: Map<String, String>)
127
}
128
129
/**
130
* Call logging plugin for HTTP request/response logging
131
*/
132
object CallLogging : ApplicationPlugin<CallLoggingConfig>
133
```
134
135
### Origin Connection Point
136
137
Data class for representing connection point information, useful for proxy scenarios.
138
139
```kotlin { .api }
140
/**
141
* Represents connection point information for forwarded requests
142
*/
143
data class OriginConnectionPoint(
144
/** URI scheme (http, https) */
145
val scheme: String,
146
147
/** Host name */
148
val host: String,
149
150
/** Port number */
151
val port: Int,
152
153
/** HTTP version */
154
val version: String
155
)
156
```
157
158
### Parameter Utilities
159
160
Enhanced parameter handling and URL building utilities.
161
162
```kotlin { .api }
163
/**
164
* Type alias for parameter collections (string-based key-value pairs)
165
*/
166
typealias Parameters = StringValues
167
168
/**
169
* Get parameter or throw exception if missing
170
*/
171
fun Parameters.getOrFail(name: String): String
172
173
/**
174
* Encode parameters to URL format
175
*/
176
fun encodeParameters(parameters: Parameters): String
177
178
/**
179
* Parse URL-encoded parameters
180
*/
181
fun parseParameters(encoded: String): Parameters
182
```
183
184
### URL Building
185
186
Utilities for building URLs relative to current request context.
187
188
```kotlin { .api }
189
/**
190
* Build URLs relative to current request
191
*/
192
fun ApplicationCall.url(block: URLBuilder.() -> Unit = {}): String
193
194
/**
195
* Build paths relative to current request
196
*/
197
fun ApplicationCall.path(vararg segments: String): String
198
199
/**
200
* Build URL with specific path segments
201
*/
202
fun ApplicationCall.href(path: String, block: URLBuilder.() -> Unit = {}): String
203
```
204
205
### Path Utilities
206
207
Utilities for path manipulation and normalization.
208
209
```kotlin { .api }
210
/**
211
* Combine path segments safely
212
*/
213
fun combinePath(vararg segments: String): String
214
215
/**
216
* Resolve relative paths
217
*/
218
fun resolvePath(base: String, relative: String): String
219
220
/**
221
* Normalize and relativize paths
222
*/
223
fun normalizeAndRelativize(path: String): String
224
```
225
226
### Thread-Safe Collections
227
228
Copy-on-write hash map implementation for thread-safe operations.
229
230
```kotlin { .api }
231
/**
232
* Thread-safe copy-on-write hash map implementation
233
*/
234
class CopyOnWriteHashMap<K, V> : MutableMap<K, V> {
235
/** Get value by key */
236
override fun get(key: K): V?
237
238
/** Put key-value pair, returns previous value */
239
override fun put(key: K, value: V): V?
240
241
/** Remove key, returns previous value */
242
override fun remove(key: K): V?
243
244
/** Clear all entries */
245
override fun clear()
246
247
/** Get current size */
248
override val size: Int
249
250
/** Check if empty */
251
override fun isEmpty(): Boolean
252
253
/** Check if contains key */
254
override fun containsKey(key: K): Boolean
255
256
/** Check if contains value */
257
override fun containsValue(value: V): Boolean
258
259
/** Get all keys */
260
override val keys: MutableSet<K>
261
262
/** Get all values */
263
override val values: MutableCollection<V>
264
265
/** Get all entries */
266
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
267
}
268
```
269
270
### Data Conversion Plugin
271
272
Plugin for automatic data type conversion.
273
274
```kotlin { .api }
275
/**
276
* Plugin for data conversion between types
277
*/
278
object DataConversion : ApplicationPlugin<DataConversionConfig>
279
280
/**
281
* Configuration for data conversion
282
*/
283
class DataConversionConfig {
284
/** Add conversion from one type to another */
285
fun <T, R> convert(from: KClass<T>, to: KClass<R>, converter: (T) -> R)
286
287
/** Add conversion with conditions */
288
fun <T, R> convert(
289
from: KClass<T>,
290
to: KClass<R>,
291
predicate: (T) -> Boolean,
292
converter: (T) -> R
293
)
294
}
295
```
296
297
### Error Handling Utilities
298
299
Exception classes for common HTTP error scenarios.
300
301
```kotlin { .api }
302
/**
303
* Exception for bad requests (400)
304
*/
305
class BadRequestException(message: String = "Bad Request", cause: Throwable? = null) : Exception(message, cause)
306
307
/**
308
* Exception for not found resources (404)
309
*/
310
class NotFoundException(message: String = "Not Found", cause: Throwable? = null) : Exception(message, cause)
311
312
/**
313
* Exception when content cannot be transformed to requested type
314
*/
315
class CannotTransformContentToTypeException(
316
message: String,
317
cause: Throwable? = null
318
) : Exception(message, cause)
319
320
/**
321
* Exception for unsupported media types (415)
322
*/
323
class UnsupportedMediaTypeException(
324
contentType: ContentType,
325
cause: Throwable? = null
326
) : Exception("Unsupported media type: $contentType", cause)
327
```
328
329
## Usage Examples
330
331
### HTTP Link Headers
332
333
```kotlin
334
import io.ktor.server.application.*
335
import io.ktor.server.http.*
336
import io.ktor.server.response.*
337
import io.ktor.server.routing.*
338
339
fun Application.linkHeaders() {
340
routing {
341
get("/article/{id}") {
342
val articleId = call.parameters["id"]
343
344
// Add link headers for related resources
345
call.response.linkHeader(
346
uri = "/api/articles/$articleId",
347
rel = "canonical"
348
)
349
350
call.response.linkHeader(Link(
351
uri = "/articles/$articleId/comments",
352
rel = listOf("related", "comments"),
353
type = ContentType.Application.Json,
354
title = "Article Comments"
355
))
356
357
call.response.linkHeader(
358
uri = "/css/article.css",
359
rel = "stylesheet",
360
type = ContentType.Text.CSS
361
)
362
363
call.respondText("Article $articleId")
364
}
365
366
get("/api/users") {
367
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
368
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 20
369
370
// Pagination links
371
if (page > 1) {
372
call.response.linkHeader(
373
uri = "/api/users?page=${page - 1}&limit=$limit",
374
rel = "prev"
375
)
376
}
377
378
call.response.linkHeader(
379
uri = "/api/users?page=${page + 1}&limit=$limit",
380
rel = "next"
381
)
382
383
call.response.linkHeader(
384
uri = "/api/users?page=1&limit=$limit",
385
rel = "first"
386
)
387
388
call.respond(emptyList<String>()) // Mock response
389
}
390
}
391
}
392
```
393
394
### HTTP/2 Server Push
395
396
```kotlin
397
import io.ktor.server.application.*
398
import io.ktor.server.http.*
399
import io.ktor.server.response.*
400
import io.ktor.server.routing.*
401
402
fun Application.serverPush() {
403
routing {
404
get("/") {
405
// Push related resources before sending main response
406
call.push {
407
url = Url("/css/main.css")
408
method = HttpMethod.Get
409
headers.append(HttpHeaders.Accept, "text/css")
410
}
411
412
call.push {
413
url = Url("/js/main.js")
414
method = HttpMethod.Get
415
headers.append(HttpHeaders.Accept, "application/javascript")
416
}
417
418
call.push {
419
url = Url("/images/logo.png")
420
method = HttpMethod.Get
421
headers.append(HttpHeaders.Accept, "image/*")
422
}
423
424
// Send main HTML response
425
call.respondText("""
426
<!DOCTYPE html>
427
<html>
428
<head>
429
<link rel="stylesheet" href="/css/main.css">
430
<script src="/js/main.js" defer></script>
431
</head>
432
<body>
433
<img src="/images/logo.png" alt="Logo">
434
<h1>Welcome</h1>
435
</body>
436
</html>
437
""".trimIndent(), ContentType.Text.Html)
438
}
439
}
440
}
441
```
442
443
### URL Building and Path Utilities
444
445
```kotlin
446
import io.ktor.server.application.*
447
import io.ktor.server.response.*
448
import io.ktor.server.routing.*
449
import io.ktor.server.util.*
450
451
fun Application.urlBuilding() {
452
routing {
453
get("/user/{id}") {
454
val userId = call.parameters["id"]
455
456
// Build URLs relative to current request
457
val profileUrl = call.url {
458
path("profile")
459
}
460
461
val apiUrl = call.url {
462
path("api", "users", userId ?: "")
463
parameters.append("include", "profile")
464
}
465
466
val avatarPath = call.path("avatar.jpg")
467
468
val editHref = call.href("/edit") {
469
parameters.append("id", userId ?: "")
470
}
471
472
call.respond(mapOf(
473
"userId" to userId,
474
"profileUrl" to profileUrl,
475
"apiUrl" to apiUrl,
476
"avatarPath" to avatarPath,
477
"editHref" to editHref
478
))
479
}
480
481
get("/docs/{...}") {
482
val pathSegments = call.parameters.getAll("...")
483
val docPath = pathSegments?.joinToString("/") ?: ""
484
485
// Path utilities
486
val normalizedPath = normalizeAndRelativize(docPath)
487
val combinedPath = combinePath("docs", "v1", normalizedPath)
488
val resolvedPath = resolvePath("/documentation", combinedPath)
489
490
call.respond(mapOf(
491
"originalPath" to docPath,
492
"normalizedPath" to normalizedPath,
493
"combinedPath" to combinedPath,
494
"resolvedPath" to resolvedPath
495
))
496
}
497
}
498
}
499
```
500
501
### Parameter Handling
502
503
```kotlin
504
import io.ktor.server.application.*
505
import io.ktor.server.response.*
506
import io.ktor.server.routing.*
507
import io.ktor.server.util.*
508
509
fun Application.parameterHandling() {
510
routing {
511
get("/required") {
512
try {
513
val userId = call.request.queryParameters.getOrFail("userId")
514
val action = call.request.queryParameters.getOrFail("action")
515
516
call.respond(mapOf(
517
"userId" to userId,
518
"action" to action
519
))
520
521
} catch (e: IllegalArgumentException) {
522
call.respond(HttpStatusCode.BadRequest, "Missing required parameters")
523
}
524
}
525
526
post("/form") {
527
val formParams = call.receiveParameters()
528
529
// Encode parameters back to URL format
530
val encoded = encodeParameters(formParams)
531
532
call.respond(mapOf(
533
"received" to formParams.toMap(),
534
"encoded" to encoded
535
))
536
}
537
}
538
}
539
```
540
541
### Thread-Safe Data Structures
542
543
```kotlin
544
import io.ktor.server.application.*
545
import io.ktor.server.response.*
546
import io.ktor.server.routing.*
547
import io.ktor.server.util.*
548
549
class SessionManager {
550
private val sessions = CopyOnWriteHashMap<String, SessionData>()
551
552
fun createSession(sessionId: String, data: SessionData) {
553
sessions[sessionId] = data
554
}
555
556
fun getSession(sessionId: String): SessionData? {
557
return sessions[sessionId]
558
}
559
560
fun removeSession(sessionId: String): SessionData? {
561
return sessions.remove(sessionId)
562
}
563
564
fun getAllSessions(): Map<String, SessionData> {
565
return sessions.toMap()
566
}
567
568
fun getActiveSessionCount(): Int {
569
return sessions.size
570
}
571
}
572
573
data class SessionData(
574
val userId: String,
575
val createdAt: Long,
576
val lastAccessedAt: Long
577
)
578
579
fun Application.sessionHandling() {
580
val sessionManager = SessionManager()
581
582
routing {
583
post("/login") {
584
val sessionId = generateSessionId()
585
val sessionData = SessionData(
586
userId = "user123",
587
createdAt = System.currentTimeMillis(),
588
lastAccessedAt = System.currentTimeMillis()
589
)
590
591
sessionManager.createSession(sessionId, sessionData)
592
593
call.respond(mapOf(
594
"sessionId" to sessionId,
595
"activeSessions" to sessionManager.getActiveSessionCount()
596
))
597
}
598
599
get("/session/{id}") {
600
val sessionId = call.parameters["id"]
601
val session = sessionManager.getSession(sessionId ?: "")
602
603
if (session != null) {
604
call.respond(session)
605
} else {
606
call.respond(HttpStatusCode.NotFound, "Session not found")
607
}
608
}
609
}
610
}
611
612
private fun generateSessionId(): String {
613
return java.util.UUID.randomUUID().toString()
614
}
615
```
616
617
### Error Handling
618
619
```kotlin
620
import io.ktor.server.application.*
621
import io.ktor.server.plugins.*
622
import io.ktor.server.response.*
623
import io.ktor.server.routing.*
624
625
fun Application.errorHandling() {
626
routing {
627
get("/validate/{value}") {
628
val value = call.parameters["value"]
629
630
if (value.isNullOrBlank()) {
631
throw BadRequestException("Value parameter is required")
632
}
633
634
val intValue = value.toIntOrNull()
635
?: throw BadRequestException("Value must be a valid integer")
636
637
if (intValue < 0) {
638
throw BadRequestException("Value must be positive")
639
}
640
641
call.respond(mapOf("validatedValue" to intValue))
642
}
643
644
get("/resource/{id}") {
645
val resourceId = call.parameters["id"]
646
val resource = findResourceById(resourceId)
647
648
if (resource == null) {
649
throw NotFoundException("Resource with ID $resourceId not found")
650
}
651
652
call.respond(resource)
653
}
654
655
post("/convert") {
656
val contentType = call.request.contentType
657
658
when {
659
contentType.match(ContentType.Application.Json) -> {
660
try {
661
val data = call.receive<Map<String, Any>>()
662
call.respond(data)
663
} catch (e: Exception) {
664
throw CannotTransformContentToTypeException(
665
"Failed to parse JSON content",
666
e
667
)
668
}
669
}
670
671
contentType.match(ContentType.Application.Xml) -> {
672
throw UnsupportedMediaTypeException(contentType)
673
}
674
675
else -> {
676
throw UnsupportedMediaTypeException(contentType)
677
}
678
}
679
}
680
}
681
}
682
683
private fun findResourceById(id: String?): Map<String, Any>? {
684
// Mock implementation
685
return if (id == "123") {
686
mapOf("id" to id, "name" to "Sample Resource")
687
} else {
688
null
689
}
690
}
691
```
692
693
### Data Conversion
694
695
```kotlin
696
import io.ktor.server.application.*
697
import io.ktor.server.plugins.*
698
import io.ktor.server.response.*
699
import io.ktor.server.routing.*
700
701
data class User(val id: Int, val name: String, val email: String)
702
data class UserDto(val id: String, val displayName: String, val contactEmail: String)
703
704
fun Application.dataConversion() {
705
install(DataConversion) {
706
// Convert User to UserDto
707
convert<User, UserDto> { user ->
708
UserDto(
709
id = user.id.toString(),
710
displayName = user.name,
711
contactEmail = user.email
712
)
713
}
714
715
// Convert String to Int with validation
716
convert<String, Int>(
717
predicate = { it.matches(Regex("\\d+")) }
718
) { it.toInt() }
719
720
// Convert String to Boolean
721
convert<String, Boolean> { str ->
722
when (str.lowercase()) {
723
"true", "yes", "1" -> true
724
"false", "no", "0" -> false
725
else -> throw IllegalArgumentException("Invalid boolean value: $str")
726
}
727
}
728
}
729
730
routing {
731
get("/user/{id}") {
732
val userId = call.parameters["id"]?.toIntOrNull()
733
?: return@get call.respond(HttpStatusCode.BadRequest)
734
735
val user = User(userId, "John Doe", "john@example.com")
736
737
// DataConversion will automatically convert User to UserDto
738
call.respond(user)
739
}
740
}
741
}
742
```
743
744
### Logging and MDC
745
746
```kotlin
747
import io.ktor.server.application.*
748
import io.ktor.server.logging.*
749
import io.ktor.server.response.*
750
import io.ktor.server.routing.*
751
752
fun Application.loggingAndMdc() {
753
install(CallLogging) {
754
level = Level.INFO
755
756
mdc("requestId") {
757
java.util.UUID.randomUUID().toString()
758
}
759
760
mdc("userId") { call ->
761
call.request.headers["X-User-ID"]
762
}
763
764
filter { call ->
765
!call.request.path().startsWith("/health")
766
}
767
}
768
769
routing {
770
get("/api/data") {
771
val mdcProvider = application.environment.monitor.mdcProvider
772
773
// Add custom MDC values
774
mdcProvider?.put("operation", "fetchData")
775
mdcProvider?.put("startTime", System.currentTimeMillis().toString())
776
777
try {
778
// Simulate data fetching
779
val data = fetchData()
780
781
mdcProvider?.put("recordCount", data.size.toString())
782
application.environment.log.info("Data fetch completed successfully")
783
784
call.respond(data)
785
786
} catch (e: Exception) {
787
mdcProvider?.put("error", e.message ?: "Unknown error")
788
application.environment.log.error("Data fetch failed", e)
789
790
call.respond(HttpStatusCode.InternalServerError, "Data fetch failed")
791
} finally {
792
mdcProvider?.remove("operation")
793
mdcProvider?.remove("startTime")
794
mdcProvider?.remove("recordCount")
795
mdcProvider?.remove("error")
796
}
797
}
798
}
799
}
800
801
private fun fetchData(): List<String> {
802
// Mock data fetching
803
return listOf("item1", "item2", "item3")
804
}
805
```