为典型的 REST 实现嵌套 Ktor 路由匹配器和处理程序的正确方法是啥?

Posted

技术标签:

【中文标题】为典型的 REST 实现嵌套 Ktor 路由匹配器和处理程序的正确方法是啥?【英文标题】:What's the correct way to nest Ktor route matchers and handlers for a typical REST implementation?为典型的 REST 实现嵌套 Ktor 路由匹配器和处理程序的正确方法是什么? 【发布时间】:2018-03-28 18:34:44 【问题描述】:

我在理解将 Ktor 的 DSL 用于request routing 的正确方法时遇到了一些麻烦。 问题是,当我测试我的 API 并尝试 GET /nomenclature/articles/categories 应该返回所有文章类别的列表时,我得到的是 Invalid article specifiedarticleId 参数无效时我为 /nomenclature/articles/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)
                
            
        
    

【问题讨论】:

【参考方案1】:

原因是您没有为GET /nomenclature/articles/categories 指定任何处理程序。

您为/articles 设置路由,然后为/categories 设置路由,但内部没有处理程序。 里面唯一的就是get("categoryId?"),不匹配,因为没有tailcard。

得到/nomenclature/articles/articleId的原因是,它首先尝试匹配/articles,但由于它无法匹配/categories(没有处理程序),它继续寻找,最后找到最后一条路线,其中包含通配符参数和匹配项。

如果您想为该特定路由设置处理程序,方法如下:

route("articles") 
    route("categories") 
        get("categoryId?") 
            ...
        
        get 
            ... your code ...
        
    

【讨论】:

以上是关于为典型的 REST 实现嵌套 Ktor 路由匹配器和处理程序的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

没有 PK 的 Django REST Framework 嵌套路由

KTOR DSL - 如何嵌套模板?

ZF_react react-router 使用正则匹配路由,Switch路由,嵌套路由的实现 路由保护 NavLink withRouter

如何在 ktor 框架中限制路由访问?

嵌套路由上的 Django REST 权限

如何在 Django Rest Framework 中过滤嵌套的序列化程序?