Какой правильный способ вложить сопоставители и обработчики маршрутов Ktor для типичной реализации REST?

У меня проблемы с пониманием того, как правильно использовать DSL Ktor для маршрутизация запросов. Проблема в том, что когда я тестирую свой API и пытаюсь GET /nomenclature/articles/categories, который должен вернуть список всех категорий статей, вместо этого я получаю Invalid article specified сообщение, которое я возвращаю для маршрута /nomenclature/articles/{articleId}, когда параметр articleId недействителен.

Вот мой код:

route("/nomenclature") {
    method(HttpMethod.Get) {
        handle { call.respondText("The resource you accessed is not a valid REST resource, but a parent node. Children nodes include articles, categories, subcategories and so on.") }
    }
    route("articles") {
        route("categories") {
            get("{categoryId?}") {
                val categoryIdParam = call.parameters["categoryId"]
                if (categoryIdParam != null) {
                    val categoryId = categoryIdParam.toIntOrNull()
                    if (categoryId != null) {
                        val category = articlesDAO.findCategoryById(categoryId)
                        if (category != null) call.respond(category)
                        else call.respondText("Category not found", status = HttpStatusCode.NotFound)
                    } else call.respondText("Invalid category ID specified", status = HttpStatusCode.BadRequest)
                } else {
                    val categories = articlesDAO.getCategories()
                    if (categories != null) call.respond(categories)
                    else call.respondText("No categories found", status = HttpStatusCode.NotFound)
                }
            }
        }
        route("subcategories") {
            get("{subcategoryId?}") {
                val subcategoryIdParam = call.parameters["subcategoryId"]
                if (subcategoryIdParam != null) {
                    val subcategoryId = subcategoryIdParam.toIntOrNull()
                    if (subcategoryId != null) {
                        val subcategory = articlesDAO.findSubcategoryById(subcategoryId)
                        if (subcategory != null) call.respond(subcategory)
                        else call.respondText("Subcategory not found", status = HttpStatusCode.NotFound)
                    } else call.respondText("Invalid subcategory ID specified", status = HttpStatusCode.BadRequest)
                } else {
                    val subcategories = articlesDAO.getCategories()
                    if (subcategories != null) call.respond(subcategories)
                    else call.respondText("No subcategories found", status = HttpStatusCode.NotFound)
                }
            }
        }
        get("types") {
            val articleTypes = articlesDAO.getArticleTypes()
            if (articleTypes != null) call.respond(articleTypes)
            else call.respondText("No article types found", status = HttpStatusCode.NotFound)
        }
        get("wholePackagings") {
            val wholePackagings = articlesDAO.getWholePackagings()
            if (wholePackagings != null) call.respond(wholePackagings)
            else call.respondText("No whole packagings found", status = HttpStatusCode.NotFound)
        }
        get("fractionsPackagings") {
            val fractionsPackagings = articlesDAO.getFractionPackagings()
            if (fractionsPackagings != null) call.respond(fractionsPackagings)
            else call.respondText("No fractions packagings found", status = HttpStatusCode.NotFound)
        }
        get("{articleId?}") {
            val articleIdParam = call.parameters["articleId"]
            if (articleIdParam != null) {
                val articleId = articleIdParam.toIntOrNull()
                if (articleId != null) {
                    val article = articlesDAO.findArticleById(articleId)
                    if (article != null) call.respond(article) else call.respondText("No article found", status = HttpStatusCode.NotFound)
                } else call.respondText("Invalid article ID specified", status = HttpStatusCode.BadRequest)
            } else {
                val articles = articlesDAO.getArticles()
                if (articles != null) call.respond(articles) else call.respondText("No articles found", status = HttpStatusCode.NotFound)
            }
        }
    }
}

Было бы здорово, если бы кто-нибудь мог мне помочь, предоставив базовый, но в некоторой степени исчерпывающий пример того, как использовать все сопоставители и обработчики маршрутов Ktor, в том числе во вложенных ресурсах.

РЕДАКТИРОВАТЬ: я переписал маршрутизацию, используя другой подход, но мне все равно хотелось бы знать, почему моя предыдущая версия не работала должным образом. Вот мой второй подход, который, похоже, работает, как ожидалось (я его тестировал):

routing {
    route("/v1") {
        route("stock") {
            get {
                val stock = stockDAO.getAllStock()
                if (stock != null) call.respond(stock) else call.respondText("No stock found", status = HttpStatusCode.NotFound)
            }
            get("{locationId}") {
                val locationIdParam = call.parameters["locationId"]
                if (locationIdParam != null) {
                    val locationId = locationIdParam.toIntOrNull()
                    if (locationId != null) {
                        val stock = stockDAO.getStockByLocationId(locationId)
                        if (stock != null) call.respond(stock) else call.respondText("No stock found", status = HttpStatusCode.NotFound)
                    } else call.respondText("ERROR: Invalid location ID specified.", status = HttpStatusCode.BadRequest)
                }
            }
        }

        route("nomenclature") {
            get {
                call.respondText("The resource you accessed is not a valid REST resource, but a parent node. Children nodes include articles, categories, subcategories and so on.")
            }

            route("articles") {
                get {
                    val articles = articlesDAO.getArticles()
                    if (articles != null) call.respond(articles) else call.respondText("No articles found", status = HttpStatusCode.NotFound)
                }
                get("{articleId}") {
                    val articleIdParam = call.parameters["articleId"]
                    if (articleIdParam != null) {
                        val articleId = articleIdParam.toIntOrNull()
                        if (articleId != null) {
                            val article = articlesDAO.findArticleById(articleId)
                            if (article != null) call.respond(article) else call.respondText("No article found", status = HttpStatusCode.NotFound)
                        } else call.respondText("Invalid article ID specified", status = HttpStatusCode.BadRequest)
                    }
                }

                route("categories") {
                    get {
                        val categories = articlesDAO.getCategories()
                        if (categories != null) call.respond(categories)
                        else call.respondText("No categories found", status = HttpStatusCode.NotFound)
                    }
                    get("{categoryId}") {
                        val categoryIdParam = call.parameters["categoryId"]
                        if (categoryIdParam != null) {
                            val categoryId = categoryIdParam.toIntOrNull()
                            if (categoryId != null) {
                                val category = articlesDAO.findCategoryById(categoryId)
                                if (category != null) call.respond(category)
                                else call.respondText("Category not found", status = HttpStatusCode.NotFound)
                            } else call.respondText("Invalid category ID specified", status = HttpStatusCode.BadRequest)
                        }
                    }
                }

                route("subcategories") {
                    get {
                        val subcategories = articlesDAO.getSubcategories()
                        if (subcategories != null) call.respond(subcategories)
                        else call.respondText("No subcategories found", status = HttpStatusCode.NotFound)
                    }
                    get("{subcategoryId}") {
                        val subcategoryIdParam = call.parameters["subcategoryId"]
                        if (subcategoryIdParam != null) {
                            val subcategoryId = subcategoryIdParam.toIntOrNull()
                            if (subcategoryId != null) {
                                val subcategory = articlesDAO.findSubcategoryById(subcategoryId)
                                if (subcategory != null) call.respond(subcategory)
                                else call.respondText("Subcategory not found", status = HttpStatusCode.NotFound)
                            } else call.respondText("Invalid subcategory ID specified", status = HttpStatusCode.BadRequest)
                        }
                    }
                }

                get("types") {
                    val articleTypes = articlesDAO.getArticleTypes()
                    if (articleTypes != null) call.respond(articleTypes)
                    else call.respondText("No article types found", status = HttpStatusCode.NotFound)
                }
                get("wholePackagings") {
                    val wholePackagings = articlesDAO.getWholePackagings()
                    if (wholePackagings != null) call.respond(wholePackagings)
                    else call.respondText("No whole packagings found", status = HttpStatusCode.NotFound)
                }
                get("fractionsPackagings") {
                    val fractionsPackagings = articlesDAO.getFractionPackagings()
                    if (fractionsPackagings != null) call.respond(fractionsPackagings)
                    else call.respondText("No fractions packagings found", status = HttpStatusCode.NotFound)
                }
            }
        }
    }
}

person Paul-Sebastian Manole    schedule 17.10.2017    source источник


Ответы (1)


Причина в том, что вы не указали обработчик для GET /nomenclature/articles/categories.

Вы настраиваете маршрут для /articles, а затем для /categories, но внутри нет обработчика. Единственное, что внутри - это get("{categoryId?}"), что не совпадает, так как нет хвостовой карты.

Причина получения /nomenclature/articles/{articleId} заключается в том, что он сначала пытается сопоставить /articles, но, поскольку он не может сопоставить /categories (нет обработчика), он продолжает поиск и, наконец, находит последний маршрут, который содержит параметр подстановочного знака и соответствует.

Если вы хотите настроить обработчик для этого конкретного маршрута, вот как это сделать:

route("articles") {
    route("categories") {
        get("{categoryId?}") {
            ...
        }
        get {
            ... your code ...
        }
    }
}
person avolkmann    schedule 08.01.2018