Bit of a lazyweb question, but does anyone know of...
# server
r
Bit of a lazyweb question, but does anyone know off the top of their head whether ktor or http4k make it easy to retrieve a list of all the endpoints they expose as a method / uri template pair?
d
Http4k doesnt have this unless you use the contract module, in which case you can get it easily through the generated open API dox
You'd have to process the JSON but that's fairly trivial using eg curl and jq
r
Thanks
h
s
Even if you're not using open API, in http4k you can get routing description (see example). As it is, it'd also require some parsing to turn into a list of Method/URI pair though.
d
@s4nchez ah! I love it when I learn new things about http4k... 😂
😂 1
r
Thanks all, good to know
d
We had a similar issue as we wanted to avoid duplicated routes. Ktor doesn't prevent that by default, so we resorted to the following solution:
Copy code
// Find duplicate routes and don't start if there are any.
// The logic tries to only look at the relevant aspects, i.e. only the actual path and HTTP method.
fun Application.avoidDuplicateRoutes() {
    routing {
        var rootRoute: Route = children.first()
        while (rootRoute.parent != null) {
            rootRoute = rootRoute.parent!!
        }
        allRoutes(rootRoute).filter { it.selector is HttpMethodRouteSelector }
            .map { it.toString() }
            .map { normalize(it) }
            .sorted()
            .onEach { <http://logger.info|logger.info>("Found route $it") }
            .groupingBy { it }
            .eachCount()
            .filter { it.value > 1 }
            .forEach { (route, _) ->
                throw IllegalStateException("There are multiple routes for the path $route")
            }
    }
}

// remove all authentication and authorization routes, we are only interested in the actual paths
private fun normalize(routeString: String): String {
    return routeString.replace(Regex("\\/\\(authenticate[ a-zA-Z,]*\\)"), "")
        .replace(Regex("\\/\\(authorize[ a-zA-Z_,]*\\)"), "")
        .replace(Regex("\\{[\\w\\?]+\\}"), "{}")
}

private fun allRoutes(root: Route): List<Route> {
    return listOf(root) + root.children.flatMap { allRoutes(it) }
}
👍 1
d
Deduplicating of routes would actually force a library to encode them statically, so this isn't necessarily surprising. In http4k, the routes don't have to be declared upfront - not only can they be both dynamically created on construction, but the routing tree can be modified at runtime if required. This is possible because matching of a route to a request is entirely done based on predicate functions (aka `Router`s) and not based on static strings. It is unusual to need this, but it is possible. (There's probably a talk in there somewhere... 😂 )
d
That's true and fully fair. Our usecase is that we declare routes in different modules, "wired" by Koin. That did lead to the situation that we had the same route twice (accidentally) and it took us quite a while to realize the source of the problem. With the above snippet we manage to at least solve our specific issue 🙂 If you do dynamic routing with e.g. routes only being valid at specific times or magic like that, well, you better know what you are doing 🙂