How to list configured routes in Ktor - ktor

I am setting up a project with multiple modules that contain different versions of api.
To verify correct route configuration I would like to print configured routes to application log like it is done in spring framework.
Is it possible and what should I use for that?

You can do it by recursively traversing routes starting from the root all the way down the tree and filtering in only ones with the HTTP method selector. This solution is described here.
fun Application.module() {
// routing configuration goes here
val root = feature(Routing)
val allRoutes = allRoutes(root)
val allRoutesWithMethod = allRoutes.filter { it.selector is HttpMethodRouteSelector }
allRoutesWithMethod.forEach {
println("route: $it")
}
}
fun allRoutes(root: Route): List<Route> {
return listOf(root) + root.children.flatMap { allRoutes(it) }
}
Please note that the code above must be placed after the routing configuration.

Related

Ktor Resources type-safe routing plugin to generate URLs and paths

I have recently updated current Ktor version to 2.1.x and Locations plugin is now deprecated. I'm refactoring code to use Resources plugin for type-safe routing. The documentation is lacking how to generate paths from Resources objects. I'm trying to create a redirect address based on a Resource and achieve a functionality like in Locations plugin to build URLs
val path = application.locations.href(Listing(name = "movies", page = 10, count = 20))
Resource definition
#Serializable
#Resource("clients")
class Clients() {
#Serializable
#Resource("{clientID}")
class ID(val parent: Clients, val clientID: Int) {}
}
Route definition
fun Route.clientRouting(dao: ClientDao) {
route("") {
get<Clients> {
call.respondText("Hello Client!")
}
get<Clients.ID> { clientID ->
call.respondRedirect(...) //Redirect to Clients route
}
}
}
So... Is there a standard way of generating paths and URLs from Resources?
You can call the href method on an Application instance to get a URL for a specified resource:
get<Clients.ID> { clientID ->
call.respondRedirect(call.application.href(Clients()))
}

Why do I need to reference a custom gradle config with square brackets?

I created a gradle build config just to download some dependencies. The documentation has been sparse, so I've piece together this working snippet based on random snippets and guesses.
configurations {
create("downloadDeps")
}
dependencies {
// JSON
configurations["downloadDeps"]("com.fasterxml.jackson.core:jackson-databind:2.13.3")
configurations["downloadDeps"]("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3")
}
repositories {
// internal repository
maven {
url = uri("...")
credentials {
username = System.getenv("ARTIFACTORY_USER") ?: System.getProperty("ARTIFACTORY_USER") as String
password = System.getenv("ARTIFACTORY_TOKEN") ?: System.getProperty("ARTIFACTORY_TOKEN") as String
}
}
}
tasks.register<Copy> ("downloadDeps") {
from(configurations["downloadDeps"])
into("lib/")
}
If I reference the "downloadDeps" dependency like configuration.downloadDeps or downloadDeps("com.fasterxml.jackson.core:jackson-databind:2.13.3"). I get an error about an unresolved reference to "downloadDeps".
Why does implementation("...") or configuration.implementation.get() work?
The documentation #Slaw provided helped me understand why I can do something like this:
implementation("group:artifact:1.0.0")
but not
myCustomConfig("group:artifact:1.0.0")
implementation being declared that way is supported because it comes from a plugin (the Kotlin/Java plugins)
The simplest way to associate a dependency with myCustomConfig would be to do this (see these docs):
"myCustomConfig"("group:artifact:1.0.0")

How to Run Ktor Embedded Server from Code

I've written a simple Ktor server that processes a JSON payload from an incoming POST request. Now, I want to spawn this server from another application, and after processing the request, shut it down.
So the first problem I need to solve is: how do I spawn the Ktor server from some other 'driver' Kotlin code? All the tutorials I've found online are 1-2 years old, and are apparently using an older version of Ktor, where the main class looks like this:
fun main(args: Array<String>) {
embeddedServer(Netty, 8080) {
routing {
get("/") {
call.respondText("Hello from Kotlin Backend", ContentType.Text.Html)
}
}
}.start(wait = true)
}
It's easy to see that one can just run the embeddedServer(Netty, 8080) { ... }.start(wait = true) from wherever they want, to spawn the server. But I downloaded the Ktor plugin for IntelliJ IDEA yesterday, and it seems things have changed lately. This is what the new main class looks like:
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused") // Referenced in application.conf
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson {
}
}
routing {
get("/") {
call.respondText("HELLO WORLD!", contentType = ContentType.Text.Plain)
}
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
}
Now, the Application.module(...) function takes care of setting up the routing and stuff, while the actual running of the server is done internally by io.ktor.server.netty.EngineMain.main(args). Also, properties like the port number are referenced from the application.conf file, and I'm not quite sure how it figures out where to find that application.conf file.
I have been able to run this Ktor server using gradlew. I also understand that it is possible to export it as an executable jar and run it (as explained here). But I can't find out how to run it from code. Any suggestions?
Edit: And it would be nice if I could set the port number from the driver code.

How to expose swagger UI with http4k?

I'm building a microservice with the http4k framework using their Contract APIs. I can easily expose the swagger API description JSON on eg. /swagger.json with
fun app(): HttpHandler = "/" bind contract {
renderer = OpenApi3(ApiInfo("GoOut Locations API", "1.0"), Jackson)
descriptionPath = "/swagger.json"
routes += ...
}
Is there an easy way to expose the swagger UI so that 1) I can specify the path it will be available on. (eg. /swagger-ui) 2) The UI will be preconfigured to fetch the description JSON from the descriptionPath specified above.
An ideal API would look something like
fun app(): HttpHandler = "/" bind contract {
renderer = OpenApi3(ApiInfo("GoOut Locations API", "1.0"), Jackson)
descriptionPath = "/swagger.json"
uiPath = "/swagger-ui"
routes += ...
}
After a bit of searching I achieved this with combination of Web Jars and http4k's static routing.
The potential viewer of the docs must simply visit /docs path where he gets redirected to /docs/index.html?url=<path to Api description> where
index.html is a static Swagger UI entrypoint served from a web jar.
url query param tells the swagger UI where to fetch the OpenApi description from.
From the DX perspective we have a simple http4k application:
// path the OpenApi description will be exposed on
private const val API_DESCRIPTION_PATH = "/swagger.json"
fun app(): HttpHandler {
val api = contract {
renderer = OpenApi3(ApiInfo("Your API summary", "1.0"), Jackson)
descriptionPath = API_DESCRIPTION_PATH
// the actual API routes
routes += ...
}
return routes(
// the docs routes are not considered part of the API so we define them outside of the contract
swaggerUi(API_DESCRIPTION_PATH),
api
)
}
The swaggerUi handler implementation follows
/**
* Exposes Swagger UI with /docs path as its entry point.
* #param descriptionPath absolute path to API description JSON. The UI will be configured to fetch it after load.
*/
fun swaggerUi(descriptionPath: String): RoutingHttpHandler = routes(
"docs" bind Method.GET to {
Response(Status.FOUND).header("Location", "/docs/index.html?url=$descriptionPath")
},
// For some reason the static handler does not work without "/" path prefix.
"/docs" bind static(Classpath("META-INF/resources/webjars/swagger-ui/3.25.2"))
)
We also have to include the swagger-ui webjar as our dependency. Here's a Gradle directive:
implementation 'org.webjars:swagger-ui:3.25.2'
See the webjars website for Maven (and more) directives.
Note that the swaggerUi handler assumes its bound to the / root path of the whole service. However, that can be easily fixed.
As of http4k 4.28.1.0, there is now a way to do this. See the following code taken from this documentation page:
package guide.howto.create_a_swagger_ui
import org.http4k.contract.contract
import org.http4k.contract.meta
import org.http4k.contract.openapi.ApiInfo
import org.http4k.contract.openapi.v3.OpenApi3
import org.http4k.contract.ui.swaggerUi
import org.http4k.core.Body
import org.http4k.core.ContentType
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.Uri
import org.http4k.core.with
import org.http4k.lens.string
import org.http4k.routing.routes
import org.http4k.server.SunHttp
import org.http4k.server.asServer
fun main() {
val greetingLens = Body.string(ContentType.TEXT_PLAIN).toLens()
// Define a single http route for our contract
val helloHandler = "/v1/hello" meta {
operationId = "v1Hello"
summary = "Say Hello"
returning(OK, greetingLens to "Sample Greeting")
} bindContract GET to { _: Request ->
Response(OK).with(greetingLens of "HI!")
}
// Define a contract, and render an OpenApi 3 spec at "/spec"
val v1Api = contract {
routes += helloHandler
renderer = OpenApi3(
ApiInfo("Hello Server - Developer UI", "99.3.4")
)
descriptionPath = "spec"
}
// Build a Swagger UI based on the OpenApi spec defined at "/spec"
val ui = swaggerUi(
Uri.of("spec"),
title = "Hello Server",
displayOperationId = true
)
// Combine our api, spec, and ui; then start a server
// The Swagger UI is available on the root "/" path
routes(v1Api, ui)
.asServer(SunHttp(8080))
.start()
.block()
}
The solution using the webjar does not work anymore for SwaggerUI version >= 4.1.3 as the url parameter is ignored (see this issue / the release notes). The URL has to be either specified in the HTML or the url parameter needs to be enabled in the HTML.
So for now the solution seems to be to unpack the UI, update index.html, and serve directly rather through the webjar.
http4k doesn't ship with a version of the OpenApi UI. You can easily ship a version of the UI though by:
unpacking the OpenApi UI into the src/main/resources/public folder
Using a static routing block to server the resources. There is an example of that here: https://github.com/http4k/http4k-by-example/blob/22dcc9a83c497253c29830d5bc981afa5fbbe4ff/src/main/kotlin/verysecuresystems/SecuritySystem.kt#L61

Location URI with 201 response in Ktor

I can't seem to find how to build the URL of a newly created resource in Ktor.
post("/resources") {
val newResRequest = call.receive<Resource>()
val newResLocation = service.create(newResRequest)
.id
.let { constructAUrlWith(it) }
with(call) {
response.header("Location", newResLocation)
respond(Created)
}
Coming from the Spring world, where URLs can be constructed without knowing the request context or hostnames and such, I was wondering how could I achieve something similar in Ktor. Thanks in advance.