@@ -37,28 +37,40 @@ import kotlin.coroutines.EmptyCoroutineContext
37
37
public class Routing internal constructor(
38
38
internal val application : Application ,
39
39
) : Route(
40
- parent = null ,
41
- selector = RootRouteSelector ("" ),
40
+ parent = application.environment.parentRouting ,
41
+ selector = RootRouteSelector (application.environment.rootPath ),
42
42
application.environment.developmentMode,
43
43
application.environment
44
44
) {
45
45
private val tracers = mutableListOf< (RoutingResolveTrace ) -> Unit > ()
46
46
private val namedRoutes = mutableMapOf<String , Route >()
47
+ private var disposed = false
47
48
48
49
init {
49
50
addDefaultTracing()
50
51
}
51
52
52
53
public fun execute (call : ApplicationCall ) {
53
- with (application) {
54
+ var current: Routing ? = this
55
+ while (current?.disposed == true && current.parent != null ) {
56
+ current = current.parent?.routing
57
+ }
58
+ val scope = current?.application ? : return
59
+ with (scope) {
54
60
launch {
55
61
execute(call)
56
62
}
57
63
}
58
64
}
59
65
60
66
public fun unregisterNamed (name : String ) {
61
- namedRoutes.remove(name)
67
+ val route = namedRoutes.remove(name) ? : return
68
+ removeChild(route)
69
+ }
70
+
71
+ public fun unregisterPath (path : String ) {
72
+ val route = createRouteFromPath(path)
73
+ removeChild(route)
62
74
}
63
75
64
76
/* *
@@ -71,7 +83,13 @@ public class Routing internal constructor(
71
83
}
72
84
73
85
public fun dispose () {
86
+ actionOnParent { parent, _, path ->
87
+ parent.unregisterPath(path)
88
+ }
89
+ childList.clear()
90
+ namedRoutes.clear()
74
91
application.dispose()
92
+ disposed = true
75
93
}
76
94
77
95
internal fun registerNamed (name : String , route : Route ) {
@@ -81,6 +99,41 @@ public class Routing internal constructor(
81
99
namedRoutes[name] = route
82
100
}
83
101
102
+ private fun removeChild (child : Route ) {
103
+ val childParent = child.parent ? : return
104
+ val parentChildren = childParent.childList
105
+ parentChildren.remove(child)
106
+ if (parentChildren.isEmpty()) {
107
+ val toRemove = childParent.parent ? : return
108
+ removeChild(toRemove)
109
+ }
110
+ }
111
+
112
+ private fun connectWithParents (configure : Route .() -> Unit ) {
113
+ actionOnParent { parent, child, path ->
114
+ val newChild = parent.route(
115
+ path = path,
116
+ name = null ,
117
+ build = configure
118
+ )
119
+ newChild.routingRef = child.routing
120
+ }
121
+ }
122
+
123
+ private fun actionOnParent (action : (Routing , Routing , String ) -> Unit ) {
124
+ var child: Routing = this
125
+ var childParent: Route ? = child.parent ? : return
126
+ var path = child.selector.toString()
127
+ while (childParent is Routing ) {
128
+ action(childParent, child, path)
129
+
130
+ child = childParent
131
+ childParent = child.parent ? : return
132
+
133
+ path = child.selector.toString() + ' /' + path
134
+ }
135
+ }
136
+
84
137
private fun mapNameToPath (name : String , pathParameters : Parameters ): String {
85
138
val namedRoute =
86
139
namedRoutes[name]
@@ -237,7 +290,10 @@ public class Routing internal constructor(
237
290
238
291
val resolveContext = RoutingResolveContext (this , call, tracers)
239
292
when (val resolveResult = resolveContext.resolve()) {
240
- is RoutingResolveResult .Failure -> throw RouteNotFoundException (message = resolveResult.reason)
293
+ is RoutingResolveResult .Failure -> {
294
+ val routing = parent?.routing ? : throw RouteNotFoundException (message = resolveResult.reason)
295
+ routing.execute(context.call)
296
+ }
241
297
242
298
is RoutingResolveResult .Success -> {
243
299
val routingCallPipeline = resolveResult.route.buildPipeline()
@@ -257,12 +313,13 @@ public class Routing internal constructor(
257
313
* An installation object of the [Routing] plugin.
258
314
*/
259
315
@Suppress(" PublicApiImplicitType" )
260
- public companion object Plugin : BaseApplicationPlugin<Application, Routing , Routing> {
316
+ public companion object Plugin : BaseApplicationPlugin<Application, Route , Routing> {
261
317
262
318
override val key: AttributeKey <Routing > = AttributeKey (" Routing" )
263
319
264
- override fun install (pipeline : Application , configure : Routing .() -> Unit ): Routing {
320
+ override fun install (pipeline : Application , configure : Route .() -> Unit ): Routing {
265
321
val routing = Routing (pipeline).apply (configure)
322
+ routing.connectWithParents(configure)
266
323
pipeline.intercept(Call ) {
267
324
routing.interceptor(this )
268
325
}
@@ -282,22 +339,35 @@ public val Route.application: Application
282
339
)
283
340
}
284
341
342
+ public val Route .routing: Routing ?
343
+ get() = when (this ) {
344
+ is Routing -> this
345
+ else -> routingRef ? : parent?.routing
346
+ }
347
+
285
348
public fun <B : Any , F : Any > Route.install (
286
349
plugin : Plugin <Application , B , F >,
287
350
configure : B .() -> Unit = {}
288
351
): F = application.install(plugin, configure)
289
352
290
353
@KtorDsl
291
354
public fun routing (
355
+ rootPath : String = "/",
356
+ parent : Routing ? = null,
292
357
parentCoroutineContext : CoroutineContext = EmptyCoroutineContext ,
293
358
log : Logger = KtorSimpleLogger ("kotlin-routing"),
294
359
developmentMode : Boolean = false,
295
- configuration : Routing .() -> Unit
360
+ configuration : Route .() -> Unit
296
361
): Routing {
362
+ check(parent == null || rootPath != " /" ) {
363
+ " Child routing cannot have root path with '/' only. Please, provide a path to your child routing"
364
+ }
297
365
val environment = ApplicationEnvironment (
298
366
parentCoroutineContext = parentCoroutineContext,
299
- log = log,
300
367
developmentMode = developmentMode,
368
+ rootPath = rootPath,
369
+ parentRouting = parent,
370
+ log = log,
301
371
)
302
372
return with (Application (environment)) {
303
373
install(Routing , configuration)
0 commit comments