https://kotlinlang.org logo
#ktor
Title
# ktor
j

Jari Ruokonen

04/25/2022, 2:52 PM
Hello everyone! I am developing a project that I inherited and it uses Ktor. After upgrading Ktor from 1.4.2 to 1.6.8, I ran into a weird issue with unit tests. Following is an example test, but they all fail the same with 404 error.
Copy code
@Test
    fun `400 Bad request on item sync when request body cannot be parsed`() {
        val invalidItem = "invalid body"
        withTestApplication({
            restService(connector, synchronizer, logger)
        }) {
            handleRequest(true) {
                method = <http://HttpMethod.Post|HttpMethod.Post>
                uri = "/synchronize/item"
                setBody(invalidItem)
            }.apply {
                assertEquals(HttpStatusCode.BadRequest, response.status())
            }
        }
    }
This particular test fails with following status: expected: <400 Bad Request> but was: <404 Not Found> Expected : 400 Bad Request Actual : 404 Not Found If I place breakpoints to the tested restService code, they are never reached, like the 404 error hints. I also placed breakpoint to start() method of the RestService and it's being called at the start of the test as expected and I can step through it without errors. If I revert Ktor back to 1.4.2, all the tests run ok - so, what exactly could have changed from 1.4.2 to 1.6.8 that breaks these? The change log is quite lengthy, so I ask for an educated advise.
a

Aleksei Tirman [JB]

04/25/2022, 6:03 PM
It’s hard to say what can cause that. What is the implementation of the
restService
?
j

Jari Ruokonen

04/25/2022, 6:15 PM
I'll paste that first thing tomorrow. Don't have the code available now. I guess something goes wrong in the start-up of the service, but it doesn't leave any errors to log.
Copy code
fun Application.restService(
    connector: Connector,
    synchronizer: SynchronizationManager,
    logger: Logger
) {
    install(ContentNegotiation) {
        gson {
            setDateFormat(DateFormat.LONG)
            setPrettyPrinting()
        }
    }
    install(DefaultHeaders)
    install(CallLogging)

    val prometheusRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
    install(MicrometerMetrics) {
        registry = prometheusRegistry
    }

    install(Routing) {
        get("/metrics") {
            call.respond(prometheusRegistry.scrape())
        }
        route("/synchronize") {
            synchronizationRest(connector, synchronizer, logger)
        }
    }
}
There goes, as I said, nothing special. Routing is defined like this:
Copy code
fun Route.synchronizationRest(connector: Connector, synchronizer: SynchronizationManager, logger: Logger) {

    route("/item") {

        post("/") {
            try {
                ...handling is done here, likely irrelevant, as this is never reached when using Ktor 1.6.8...
                }
            } catch (e: Exception) {
                return@post call.respond(HttpStatusCode.BadRequest, e.localizedMessage)
            }
        }
    }
... other routes
}
I am in process of pinpointing which Ktor version jump is the exact one that breaks this. Could help to locate the problem, but sure is tedious chore to do.
a

Aleksei Tirman [JB]

04/26/2022, 10:47 AM
The problem is in the missing trailing
/
in the URL of the
handleRequest
call. Since 1.5.0, Ktor starts distinguish routes with trailing slash and without it. You can use the IgnoreTrailingSlash plugin to make routes with and without a trailing slash indistinguishable.
👍 1
j

Jari Ruokonen

04/26/2022, 10:54 AM
Thanks! I was sure it was something trivial, but just didn't know what.
3 Views