:question: Given 3 routes :one: `{p}/b/c` :two: `a...
# ktor
o
Given 3 routes 1️⃣
{p}/b/c
2️⃣
a/{p1}/{p2}
3️⃣
a/{...}
and request to
a/b/c
, which route is expected to be selected?
3️⃣ 1
2️⃣ 8
1️⃣ 6
t
depends on the order in which they were declared 🤔
o
no, no, please, no order dependency, it destroys application composition
j
I would say that the one with most constants/less ambiguity
o
what if I change 1️⃣ to
{p1}/b/{p2}
?
t
it destroys application composition
is there a legitimate use-case for having colliding paths like this?
k
this one is non-obvious to me too...pretty confusing
v
i vote too for 4: non-obvious
o
There is no HTTP status code for “non obvious” 🙂 How system should work?
v
😄 i would say the best way is (2), as i would read the routes from left to right. and I would always favour exact matches (here “a/“) before general matches
o
By that logic you should have voted differently on previous cases too
v
i don’t think so, let me explain my thoughts: 1) i voted for (2), as i would expect that ktor checks at first for exact routes. if that failes (as we have a trailing
/x
) i expect that ktor tries all routes that have parameter-matching, therefore using route
a/{p}/x
2) i voted for (3), as i would expect again that ktor checks all exact routes. in my opinion the HTTP-Method is “part” of the route. In my opinion one could also build routes like
GET/a/b/x POST
and
POST/a/{p}/x POST
(and doing only POST-requests) and it should behave the same.
o
Let’s get to use case
https://twitter.com/orangy/followers
This one is obviously
{user}/followers
https://twitter.com/settings/safety
This ons is likely
settings/{category}
Now let’s guess: https://twitter.com/settings/followers
(safety is one of the categories in settings)
https://twitter.com/orangy/safety is treated like
{user}/{list}
and redirects to
{user}/lists/{list}
t
i changed my answer to 2️⃣, higher priority for constants/exact matches
m
I would also expect it to be dependent on the order of the routes which were defined. This is how pattern matching usually works, isn’t it? So I’d expect #1 given the order of answers.
And I’m also used to that from other routers I’ve used in the past.
Maybe it’s possible to make that configurable? May depend on the structure and the size of your project.
o
Left-to-right, most-specific is pretty clear rule, to my taste.
👍 1
m
Yeah, unless you’re used to pattern matching (like
when
) which is by order of cases.
o
I understand there could be different use cases, hence the poll 🙂
Let’s wait few days and see the results, then discuss
m
But if order is simply not possible/feasible then most-specific to least-specific is a good alternative I think.
v
regaring the twitter example and also the possible route of `{p1}/b/{p2}`: i would expect that a router always works left-to right, as i would structure the url scheme in a hierarchical way (left has a higher hierarchy score)
after left-to-right the router should consider extracting as much information (different parameters) as possible out of the url (therefore pick
a/{p1}/{p2}
over
a/{...}
)
d
@orangy I think people are used to order based matching... nginx and popular frameworks use it. Maybe at least when declared in the same module, order should have an effect -- if not at least there should be some util that clearly shows priority resolving, I think it would be confusing for some mograting from other frameworks, they expect one thing , the framework does something else....
o
While I agree with the source of confusion, we don’t necessary need to do what everyone else did. If we were with that idea, we wouldn’t have Kotlin 😉
👍 1
d
@orangy It might be a source of unexpected behaviour, imagine having to learn all these rules that we are voting on to be able to expect certain behaviour... in Symfony, they also have this, but they have a command line tool showing all declared routes, and a debug panel to show what requests actually resolved to...
But youre right about Kotlin 😅
o
nope, rules should be simple and easy to remember. Sometimes complex situations happen, right. Debug tool is something I have in mind, but currently don’t have time to do it. Remember, docs is high priority 🙂
👍🏼 2
h
There is also the possibility of not allowing ambiguous routes. Personally if I have to use a command line tool or debugger to understand which route a call has made, then I think that's taking the wrong approach.
m
This issue reminds me of CSS Selector Matching by specificity which works as you’d expect in most cases but surprises you in a few. The problem is similar, isn’t it? https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity If it’s possible to calculate the specificity upfront it might be enough to allow the dev to .print() all routes sorted descending by specificity which allows them to quickly identify which route will match first. You could also pass a a concrete method/path/params combination to .print() to limit it to matching routes. I think not allowing ambiguous routes will not work as that would exclude many valid cases like a static handler returning all files in a path but a few specific files are generated dynamically by other routes. Or Facebook Graph’s
/{id}
+
/me
+
/favicon.ico
+
/robots.txt
and so on.
o
Correct, CSS selectors are very similar. And that
!important
also came to mind yesterday when trying to explain why simple but strict rules are important (pun intended)
But I don’t want it to work like CSS, it’s crazy complex. There is specificity, order, and !important. And sure something else I forgot.
d
So whats your thoughts? Not css like, no ambiguous routes that might sometimes be necessary...
o
There are two options, I think: 1. On each level of the imaginary folder tree, check each node and select one with best match (“a” better than “{p}” better than “{…}“). Go into that node and never come back. Answers for the poll would be 1, 2, 2 2. On each level of the tree, check a node if it matches, go into subtree and see if there is any matching route inside. From all nodes having matching subtrees, select one with the best match quality among siblings only (discard matching quality of subtree). Poll results would be 2,3,2 Currently it is implemented as similar to 2, but includes subtree quality in the mix and it is a mess. #1 is simpler, but it disallows a number of important usecases, such as catch all
{...}
route. #2 is more flexible and works very close to how it works now, but a little bit more complex to formulate. #1 is easier to implement AutoOptions and such, #2 is harder.
d
I think that each controller has a certain base path that needs to be directed to it, like in REST, so the first possibility could be implemented by manually 'mounting' a subrouter to a subpath, the second could be in case all the paths are not mounted, maybe could be the best of both worlds. And people needing simpler more expected resolution would have more control... also it would probably be more performant in the simpler cases...
m
btw, would
route("a") { route("b") { … } }
be treated differently than
route("a/b") { … }
?
d
Possibly, since a specific request was made, but then, if the same path were mounted twice, it should result in an error. It could be the better of both worlds (or maybe have an extra parameter if a mounted path should be overridable). This would give the developer a choice...