KtorJuniorTechnical
Как определять маршруты (routes) в Ktor и как работает вложенная маршрутизация?
Маршруты в Ktor определяются DSL-функциями get/post/put/delete внутри блока routing{}. Вложенность создаётся через route("/prefix") и позволяет группировать пути и применять общие плагины.
Базовая маршрутизация
Маршруты регистрируются через плагин Routing, который устанавливается автоматически при вызове routing{}. Внутри доступны функции-расширения для всех HTTP-методов:
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Welcome")
}
post("/echo") {
val body = call.receiveText()
call.respondText(body)
}
put("/items/{id}") {
val id = call.parameters["id"]
call.respondText("Updated $id")
}
delete("/items/{id}") {
val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
call.respond(HttpStatusCode.NoContent)
}
}
}
Вложенная маршрутизация
Функция route() создаёт префикс-группу. Вложенность может быть произвольной глубины:
routing {
route("/api") {
route("/v1") {
route("/users") {
get {
// GET /api/v1/users
call.respond(listOf("Alice", "Bob"))
}
post {
// POST /api/v1/users
val user = call.receive<UserDto>()
call.respond(HttpStatusCode.Created, user)
}
get("/{id}") {
// GET /api/v1/users/{id}
val id = call.parameters["id"]
call.respondText("User: $id")
}
}
}
}
}
Параметры маршрута и query-параметры
get("/search") {
// GET /search?q=kotlin&page=2
val query = call.request.queryParameters["q"] ?: ""
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
call.respondText("Query: $query, Page: $page")
}
get("/files/{path...}") {
// catch-all: /files/a/b/c -> path = "a/b/c"
val path = call.parameters.getAll("path")?.joinToString("/")
call.respondText("File path: $path")
}
Разнесение маршрутов по файлам
Рекомендуемый подход — расширения на Route:
// routes/UserRoutes.kt
fun Route.userRoutes() {
route("/users") {
get { /* ... */ }
post { /* ... */ }
}
}
// Application.kt
fun Application.configureRouting() {
routing {
route("/api/v1") {
userRoutes()
productRoutes()
}
}
}
Подводные камни
- Статические сегменты имеют приоритет над параметрическими:
/users/meдолжен быть объявлен до/users/{id}, иначе «me» будет трактоваться как id. - Catch-all параметр
{path...}должен быть последним сегментом — компилятор не запрещает иное, но маршрут просто никогда не совпадёт. - Отсутствие trailing slash обрабатывается отдельно:
/usersи/users/— разные маршруты; используйте плагинIgnoreTrailingSlashчтобы обрабатывать оба. call.parameters["id"]возвращаетString?; не забывайте обрабатывать null, иначе получите NPE или 500.- Если routing не находит совпадение — Ktor возвращает 404, но без тела. Добавьте
StatusPagesдля человекочитаемых ошибок. - Вложенный
route{}не наследует аутентификацию родителя автоматически — нужно явно оборачивать вauthenticate{}.
Common mistakes
- Путать термин «ktor routing» с соседним механизмом Ktor.
- Не называть границу lifecycle, transaction, thread или request для «ktor routing».
- Игнорировать production-эффекты «ktor routing»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «ktor routing» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «ktor routing».
- Уточнить, какие настройки или API меняют «ktor routing» в реальном сервисе.