0
# Routing System
1
2
Powerful routing DSL with pattern matching, parameter extraction, and nested route organization. Provides declarative route definition with support for HTTP methods, path patterns, and conditional routing based on headers, parameters, and other request characteristics.
3
4
## Capabilities
5
6
### Core Routing Interfaces
7
8
Base interfaces for route definition and organization.
9
10
```kotlin { .api }
11
/**
12
* Represents a route node in routing tree
13
*/
14
interface Route {
15
/** Parent route in the hierarchy */
16
val parent: Route?
17
18
/** Selector used to match this route */
19
val selector: RouteSelector
20
21
/** Whether development mode is enabled */
22
val developmentMode: Boolean
23
24
/** Application environment */
25
val environment: ApplicationEnvironment
26
}
27
28
/**
29
* Implementation of route with child routes and handlers
30
*/
31
open class RoutingNode : Route {
32
/** Child routes */
33
val children: List<RoutingNode>
34
35
/** List of handlers for this route */
36
val handlers: List<PipelineInterceptor<Unit, PipelineCall>>
37
38
/** Create child route with given selector */
39
fun createChild(selector: RouteSelector): RoutingNode
40
}
41
42
/**
43
* Root routing node for an application
44
*/
45
class Routing : RoutingNode
46
```
47
48
### Routing Builder DSL
49
50
DSL interface for constructing routes declaratively.
51
52
```kotlin { .api }
53
/**
54
* DSL builder interface for constructing routes
55
*/
56
interface RoutingBuilder {
57
/** Create child route with path pattern */
58
fun route(path: String, build: Route.() -> Unit): Route
59
60
/** Create route for specific HTTP method */
61
fun method(method: HttpMethod, body: Route.() -> Unit): Route
62
63
/** Create route matching parameter value */
64
fun param(name: String, value: String, build: Route.() -> Unit): Route
65
66
/** Create route matching optional parameter */
67
fun optionalParam(name: String, value: String, build: Route.() -> Unit): Route
68
69
/** Create route matching header value */
70
fun header(name: String, value: String, build: Route.() -> Unit): Route
71
72
/** Create route matching Accept header */
73
fun accept(contentType: ContentType, build: Route.() -> Unit): Route
74
75
/** Create route matching Content-Type header */
76
fun contentType(contentType: ContentType, build: Route.() -> Unit): Route
77
78
/** Create route matching host pattern */
79
fun host(pattern: String, build: Route.() -> Unit): Route
80
81
/** Create route matching port number */
82
fun port(port: Int, build: Route.() -> Unit): Route
83
84
/** Add handler to current route */
85
fun handle(body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit)
86
}
87
```
88
89
### HTTP Method DSL Functions
90
91
Convenient DSL functions for common HTTP methods.
92
93
```kotlin { .api }
94
/** Handle GET requests */
95
fun Route.get(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
96
97
/** Handle POST requests */
98
fun Route.post(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
99
100
/** Handle PUT requests */
101
fun Route.put(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
102
103
/** Handle DELETE requests */
104
fun Route.delete(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
105
106
/** Handle PATCH requests */
107
fun Route.patch(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
108
109
/** Handle HEAD requests */
110
fun Route.head(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
111
112
/** Handle OPTIONS requests */
113
fun Route.options(path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
114
115
/** Handle specific HTTP method */
116
fun Route.method(method: HttpMethod, path: String = "", body: suspend PipelineContext<Unit, PipelineCall>.() -> Unit): Route
117
```
118
119
### Route Selectors
120
121
Classes that define how routes are matched against incoming requests.
122
123
#### Base Route Selector
124
125
```kotlin { .api }
126
/**
127
* Base class for route selection criteria
128
*/
129
abstract class RouteSelector {
130
/** Evaluate if this selector matches the request */
131
abstract fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation
132
133
/** Quality score for route selection priority */
134
open val quality: Double
135
}
136
```
137
138
#### Path Segment Selectors
139
140
```kotlin { .api }
141
/**
142
* Matches exact path segment
143
*/
144
data class PathSegmentConstantRouteSelector(val value: String) : RouteSelector
145
146
/**
147
* Matches path segment parameter and captures value
148
*/
149
data class PathSegmentParameterRouteSelector(
150
val name: String,
151
val prefix: String? = null,
152
val suffix: String? = null
153
) : RouteSelector
154
155
/**
156
* Matches optional path segment parameter
157
*/
158
data class PathSegmentOptionalParameterRouteSelector(
159
val name: String,
160
val prefix: String? = null,
161
val suffix: String? = null
162
) : RouteSelector
163
164
/**
165
* Matches any single path segment
166
*/
167
object PathSegmentWildcardRouteSelector : RouteSelector
168
169
/**
170
* Matches remaining path segments (catch-all)
171
*/
172
object PathSegmentTailcardRouteSelector : RouteSelector
173
```
174
175
#### HTTP-Based Selectors
176
177
```kotlin { .api }
178
/**
179
* Matches HTTP method
180
*/
181
data class HttpMethodRouteSelector(val method: HttpMethod) : RouteSelector
182
183
/**
184
* Matches HTTP header value
185
*/
186
data class HttpHeaderRouteSelector(
187
val name: String,
188
val value: String
189
) : RouteSelector
190
191
/**
192
* Matches Accept header content type
193
*/
194
data class HttpAcceptRouteSelector(val contentType: ContentType) : RouteSelector
195
```
196
197
#### Connection-Based Selectors
198
199
```kotlin { .api }
200
/**
201
* Matches connection port
202
*/
203
data class LocalPortRouteSelector(val port: Int) : RouteSelector
204
205
/**
206
* Matches host pattern (supports wildcards)
207
*/
208
class HostRouteSelector(val hostPattern: String) : RouteSelector
209
```
210
211
### Route Selection Evaluation
212
213
Classes representing the result of route selector evaluation.
214
215
```kotlin { .api }
216
/**
217
* Result of route selector evaluation
218
*/
219
sealed class RouteSelectorEvaluation(val succeeded: Boolean) {
220
/**
221
* Successful match with quality score and extracted parameters
222
*/
223
data class Success(
224
val quality: Double,
225
val parameters: Parameters = Parameters.Empty,
226
val segmentIncrement: Int = 0
227
) : RouteSelectorEvaluation(true)
228
229
/**
230
* Failed match with quality score and failure status
231
*/
232
data class Failure(
233
val quality: Double,
234
val failureStatusCode: HttpStatusCode
235
) : RouteSelectorEvaluation(false)
236
237
companion object {
238
// Quality constants
239
const val qualityConstant: Double = 1.0
240
const val qualityQueryParameter: Double = 1.0
241
const val qualityParameterWithPrefixOrSuffix: Double = 0.9
242
const val qualityParameter: Double = 0.8
243
const val qualityPathParameter: Double = qualityParameter
244
const val qualityWildcard: Double = 0.5
245
const val qualityMissing: Double = 0.2
246
const val qualityTailcard: Double = 0.1
247
const val qualityTransparent: Double = -1.0
248
const val qualityFailedMethod: Double = 0.02
249
const val qualityFailedParameter: Double = 0.01
250
251
// Common evaluation results
252
val Failed: RouteSelectorEvaluation.Failure
253
val FailedPath: RouteSelectorEvaluation.Failure
254
val FailedMethod: RouteSelectorEvaluation.Failure
255
val FailedParameter: RouteSelectorEvaluation.Failure
256
val FailedAcceptHeader: RouteSelectorEvaluation.Failure
257
val Missing: RouteSelectorEvaluation
258
val Constant: RouteSelectorEvaluation
259
val Transparent: RouteSelectorEvaluation
260
val ConstantPath: RouteSelectorEvaluation
261
val WildcardPath: RouteSelectorEvaluation
262
}
263
}
264
```
265
266
### Route Resolution
267
268
Classes for resolving routes and handling the resolution process.
269
270
```kotlin { .api }
271
/**
272
* Context for route resolution process
273
*/
274
class RoutingResolveContext {
275
/** Root routing node */
276
val routing: RoutingNode
277
278
/** Current application call */
279
val call: RoutingApplicationCall
280
281
/** List of resolution tracers for debugging */
282
val tracers: List<(RoutingResolveTrace) -> Unit>
283
284
/** Captured route parameters */
285
val parameters: Parameters
286
287
/** Request headers */
288
val headers: Headers
289
}
290
291
/**
292
* Result of route resolution
293
*/
294
data class RoutingResolveResult(
295
/** Matched route */
296
val route: RoutingNode,
297
298
/** Captured parameters */
299
val parameters: Parameters,
300
301
/** Quality score of match */
302
val quality: Double
303
)
304
```
305
306
### Routing Installation
307
308
Function to install routing into an application.
309
310
```kotlin { .api }
311
/**
312
* Install and configure routing for the application
313
*/
314
fun Application.routing(configuration: Routing.() -> Unit): Routing
315
```
316
317
### Route Introspection
318
319
Extensions for examining and debugging route structures.
320
321
```kotlin { .api }
322
/** Get all descendant routes recursively */
323
val RoutingNode.allRoutes: List<RoutingNode>
324
325
/** Get all routes recursively with filtering */
326
fun RoutingNode.getAllRoutes(predicate: (RoutingNode) -> Boolean = { true }): List<RoutingNode>
327
328
/** Create route from path pattern */
329
fun createRouteFromPath(path: String): List<RouteSelector>
330
```
331
332
## Usage Examples
333
334
### Basic Routing Setup
335
336
```kotlin
337
import io.ktor.server.application.*
338
import io.ktor.server.response.*
339
import io.ktor.server.routing.*
340
341
fun Application.basicRouting() {
342
routing {
343
// Simple GET route
344
get("/") {
345
call.respondText("Hello, World!")
346
}
347
348
// Route with path parameter
349
get("/users/{id}") {
350
val id = call.parameters["id"]
351
call.respondText("User ID: $id")
352
}
353
354
// Multiple HTTP methods
355
route("/api/users") {
356
get {
357
call.respondText("Get all users")
358
}
359
360
post {
361
call.respondText("Create user")
362
}
363
364
route("/{id}") {
365
get {
366
val id = call.parameters["id"]
367
call.respondText("Get user $id")
368
}
369
370
put {
371
val id = call.parameters["id"]
372
call.respondText("Update user $id")
373
}
374
375
delete {
376
val id = call.parameters["id"]
377
call.respondText("Delete user $id")
378
}
379
}
380
}
381
}
382
}
383
```
384
385
### Advanced Path Patterns
386
387
```kotlin
388
import io.ktor.server.application.*
389
import io.ktor.server.response.*
390
import io.ktor.server.routing.*
391
392
fun Application.advancedPatterns() {
393
routing {
394
// Optional parameter
395
get("/users/{id?}") {
396
val id = call.parameters["id"]
397
if (id != null) {
398
call.respondText("User ID: $id")
399
} else {
400
call.respondText("All users")
401
}
402
}
403
404
// Parameter with prefix/suffix
405
get("/files/{name}.{ext}") {
406
val name = call.parameters["name"]
407
val ext = call.parameters["ext"]
408
call.respondText("File: $name.$ext")
409
}
410
411
// Wildcard segment
412
get("/static/*") {
413
val path = call.parameters.getAll("*")?.joinToString("/")
414
call.respondText("Static file: $path")
415
}
416
417
// Tailcard (catch remaining segments)
418
get("/docs/{...}") {
419
val pathSegments = call.parameters.getAll("...")
420
val docPath = pathSegments?.joinToString("/") ?: ""
421
call.respondText("Documentation: $docPath")
422
}
423
424
// Multiple parameters
425
get("/api/v{version}/users/{userId}/posts/{postId}") {
426
val version = call.parameters["version"]
427
val userId = call.parameters["userId"]
428
val postId = call.parameters["postId"]
429
call.respondText("API v$version: User $userId, Post $postId")
430
}
431
}
432
}
433
```
434
435
### Conditional Routing
436
437
```kotlin
438
import io.ktor.server.application.*
439
import io.ktor.server.response.*
440
import io.ktor.server.routing.*
441
442
fun Application.conditionalRouting() {
443
routing {
444
// Route based on header
445
route("/api") {
446
header("X-API-Version", "2") {
447
get("/users") {
448
call.respondText("API v2: Users")
449
}
450
}
451
452
header("X-API-Version", "1") {
453
get("/users") {
454
call.respondText("API v1: Users")
455
}
456
}
457
}
458
459
// Route based on Accept header
460
route("/data") {
461
accept(ContentType.Application.Json) {
462
get {
463
call.respond(mapOf("data" to "json"))
464
}
465
}
466
467
accept(ContentType.Application.Xml) {
468
get {
469
call.respondText("<data>xml</data>", ContentType.Application.Xml)
470
}
471
}
472
}
473
474
// Route based on Content-Type
475
route("/upload") {
476
contentType(ContentType.Application.Json) {
477
post {
478
call.respondText("JSON upload")
479
}
480
}
481
482
contentType(ContentType.MultiPart.FormData) {
483
post {
484
call.respondText("Multipart upload")
485
}
486
}
487
}
488
489
// Route based on host
490
host("api.example.com") {
491
get("/") {
492
call.respondText("API subdomain")
493
}
494
}
495
496
host("admin.example.com") {
497
get("/") {
498
call.respondText("Admin subdomain")
499
}
500
}
501
502
// Route based on port
503
port(8080) {
504
get("/dev") {
505
call.respondText("Development server")
506
}
507
}
508
509
port(443) {
510
get("/secure") {
511
call.respondText("Secure connection")
512
}
513
}
514
}
515
}
516
```
517
518
### Nested Route Organization
519
520
```kotlin
521
import io.ktor.server.application.*
522
import io.ktor.server.response.*
523
import io.ktor.server.routing.*
524
525
fun Application.nestedRouting() {
526
routing {
527
// API versioning
528
route("/api") {
529
route("/v1") {
530
route("/users") {
531
get {
532
call.respondText("V1: Get users")
533
}
534
535
post {
536
call.respondText("V1: Create user")
537
}
538
539
route("/{id}") {
540
get {
541
call.respondText("V1: Get user ${call.parameters["id"]}")
542
}
543
544
route("/posts") {
545
get {
546
call.respondText("V1: Get user posts")
547
}
548
}
549
}
550
}
551
}
552
553
route("/v2") {
554
route("/users") {
555
get {
556
call.respondText("V2: Get users")
557
}
558
559
route("/{id}") {
560
get {
561
call.respondText("V2: Get user ${call.parameters["id"]}")
562
}
563
}
564
}
565
}
566
}
567
568
// Admin section
569
route("/admin") {
570
// Apply authentication here
571
route("/users") {
572
get {
573
call.respondText("Admin: List users")
574
}
575
576
delete("/{id}") {
577
call.respondText("Admin: Delete user ${call.parameters["id"]}")
578
}
579
}
580
581
route("/settings") {
582
get {
583
call.respondText("Admin: Settings")
584
}
585
586
post {
587
call.respondText("Admin: Update settings")
588
}
589
}
590
}
591
}
592
}
593
```
594
595
### Route Parameters and Query Strings
596
597
```kotlin
598
import io.ktor.server.application.*
599
import io.ktor.server.response.*
600
import io.ktor.server.routing.*
601
602
fun Application.parameterHandling() {
603
routing {
604
get("/search") {
605
val query = call.request.queryParameters["q"] ?: ""
606
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
607
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 10
608
val sort = call.request.queryParameters["sort"] ?: "relevance"
609
610
call.respond(mapOf(
611
"query" to query,
612
"page" to page,
613
"limit" to limit,
614
"sort" to sort,
615
"results" to emptyList<String>() // Mock results
616
))
617
}
618
619
get("/users/{id}/posts") {
620
val userId = call.parameters["id"]
621
val category = call.request.queryParameters["category"]
622
val published = call.request.queryParameters["published"]?.toBoolean()
623
624
val filters = mutableMapOf<String, Any>()
625
if (category != null) filters["category"] = category
626
if (published != null) filters["published"] = published
627
628
call.respond(mapOf(
629
"userId" to userId,
630
"filters" to filters,
631
"posts" to emptyList<String>()
632
))
633
}
634
635
// Multiple parameter validation
636
get("/date/{year}/{month}/{day}") {
637
val year = call.parameters["year"]?.toIntOrNull()
638
val month = call.parameters["month"]?.toIntOrNull()
639
val day = call.parameters["day"]?.toIntOrNull()
640
641
if (year == null || month == null || day == null) {
642
call.respond(HttpStatusCode.BadRequest, "Invalid date format")
643
return@get
644
}
645
646
if (month !in 1..12 || day !in 1..31) {
647
call.respond(HttpStatusCode.BadRequest, "Invalid date values")
648
return@get
649
}
650
651
call.respondText("Date: $year-$month-$day")
652
}
653
}
654
}
655
```
656
657
### Route Introspection and Debugging
658
659
```kotlin
660
import io.ktor.server.application.*
661
import io.ktor.server.response.*
662
import io.ktor.server.routing.*
663
664
fun Application.routeIntrospection() {
665
routing {
666
get("/debug/routes") {
667
val rootRoute = this@routing
668
val allRoutes = rootRoute.getAllRoutes()
669
670
val routeInfo = allRoutes.map { route ->
671
mapOf(
672
"selector" to route.selector.toString(),
673
"hasHandlers" to route.handlers.isNotEmpty(),
674
"childCount" to route.children.size
675
)
676
}
677
678
call.respond(mapOf(
679
"totalRoutes" to allRoutes.size,
680
"routes" to routeInfo
681
))
682
}
683
684
// Route that shows current call information
685
get("/debug/call") {
686
call.respond(mapOf(
687
"method" to call.request.httpMethod.value,
688
"uri" to call.request.uri,
689
"parameters" to call.parameters.toMap(),
690
"headers" to call.request.headers.toMap()
691
))
692
}
693
}
694
}
695
```
696
697
### Custom Route Selectors
698
699
```kotlin
700
import io.ktor.server.routing.*
701
702
// Custom selector that matches based on user agent
703
class UserAgentRouteSelector(private val userAgent: String) : RouteSelector() {
704
override val quality: Double = 0.8
705
706
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
707
val requestUserAgent = context.headers["User-Agent"]
708
return if (requestUserAgent?.contains(userAgent, ignoreCase = true) == true) {
709
RouteSelectorEvaluation.Constant(quality)
710
} else {
711
RouteSelectorEvaluation.Failed
712
}
713
}
714
715
override fun toString(): String = "UserAgent($userAgent)"
716
}
717
718
// Extension function to use custom selector
719
fun Route.userAgent(userAgent: String, build: Route.() -> Unit): Route {
720
val selector = UserAgentRouteSelector(userAgent)
721
return createChild(selector).apply(build)
722
}
723
724
fun Application.customSelectors() {
725
routing {
726
userAgent("Mobile") {
727
get("/") {
728
call.respondText("Mobile version")
729
}
730
}
731
732
userAgent("Desktop") {
733
get("/") {
734
call.respondText("Desktop version")
735
}
736
}
737
738
// Default fallback
739
get("/") {
740
call.respondText("Standard version")
741
}
742
}
743
}
744
```