Structured Handling of HTTP Requests

预计阅读时间:7分钟

路由是一项安装在应用程序中的功能,用于简化和构建页面请求处理.

本页说明了路由功能. 在请求响应页面上描述了有关请求的信息的提取,以及在路由内部生成有效响应的信息.

    application.install(Routing) {
        get("/") {
            call.respondText("Hello, World!")
        }
        get("/bye") {
            call.respondText("Good bye, World!")
        }
    }

getpostputdeleteheadoptions功能是灵活而强大的路由系统的便捷快捷方式. 特别地, getroute(HttpMethod.Get, path) { handle(body) }的别名,其中body是传递给get函数的lambda.

此功能在io.ktor.routing.Routing类中io.ktor.routing.Routing ,不需要其他工件.

Routing Tree

路由通过递归匹配系统以树状组织,该递归匹配系统能够处理非常复杂的规则以处理请求. 该树由节点和选择器构成. 节点包含处理程序和拦截器,选择器连接到连接另一个节点的弧上. 如果选择器与当前路由评估上下文匹配,则算法将下至与该选择器关联的节点.

使用DSL以嵌套方式构建路由:

route("a") { // matches first segment with the value "a"
  route("b") { // matches second segment with the value "b"
     get {} // matches GET verb, and installs a handler 
     post {} // matches POST verb, and installs a handler
  }
}
method(HttpMethod.Get) { // matches GET verb
   route("a") { // matches first segment with the value "a"
      route("b") { // matches second segment with the value "b"
         handle {  } // installs handler
      }
   }
}

路由解析算法通过节点递归丢弃选择器不匹配的子树.

生成器功能:

  • route(path) –添加路径段匹配器,有关路径 ,请参见下文
  • method(verb) –添加HTTP方法匹配器.
  • param(name, value) –为查询参数的特定值添加匹配器
  • param(name) –添加匹配器,以检查查询参数是否存在并捕获其值
  • optionalParam(name) –添加匹配器以捕获查询参数的值(如果存在)
  • header(name, value) –添加匹配器,用于HTTP标头的特定值,有关质量 ,请参见下文

Path

手动构建路由树将非常不便. 因此,有一个route函数可以使用path以简单的方式覆盖大多数用例.

route功能(和相应的HTTP动词别名)接收path作为参数,该path经过处理以构建路由树. 首先,用'/'定界符将其分成路径段. 每个段都会生成一个嵌套的路由节点.

这两个变体是等效的:

route("/foo/bar") {  } // (1)

route("/foo") {
   route("bar") {  } // (2)
}

Parameters

路径还可以包含与特定路径段匹配的参数 ,并将其值捕获到应用程序调用的parameters属性中:

get("/user/{login}") {
   val login = call.parameters["login"]
}

当用户代理使用GET方法请求/user/john ,此路由将匹配,并且parameters属性将具有值为"john" "login"键.

Optional, Wildcard, Tailcard

参数和路径段可以是可选的,也可以捕获URI的其余部分.

  • {param?} –可选路径段,如果存在,则在参数中捕获
  • * –通配符,任何句段都将匹配,但不应丢失
  • {...} –尾卡,与所有URI匹配,应该位于最后. 可以为空.
  • {param...} –捕获的尾卡,匹配所有URI,并使用param作为键将每个路径段的多个值放入parameters . 使用call.parameters.getAll("param")获取所有值.

Examples:

get("/user/{login}/{fullname?}") {  } 
get("/resources/{path...}") {  } 

Quality

多个路由可以匹配到同一个HTTP请求并非不大可能.

一个示例是在Accept HTTP标头上进行匹配,该标头可以具有多个具有指定优先级(质量)的值.

accept(ContentType.Text.Plain) {  }
accept(ContentType.Text.Html) {  }

路由匹配算法不仅检查特定的HTTP请求是否匹配路由树中的特定路径,而且还计算匹配的质量并选择质量最佳的路由节点. 给定以上路由,这些路由在Accept标头上匹配,并在请求标头上Accept: text/plain; q=0.5, text/html Accept: text/plain; q=0.5, text/html将匹配text/html因为HTTP标头中的质量因数表明text/plain质量较低(默认值为1.0).

标题Accept: text/plain, text/*将匹配text/plain . 通配符匹配不如直接匹配明确. 因此,路由匹配算法将认为它们的质量较低.

另一个示例是为诸如用户之类的命名实体创建短URL,并且仍然能够偏爱诸如"设置"之类的特定页面. 一个例子是

  • https://twitter.com/kotlin – displays user “kotlin”
  • https://twitter.com/settings显示设置页面

可以这样实现:

get("/{user}") {  }
get("/settings") {  }

该参数的质量比常量字符串低,因此,即使/settings匹配,也将选择第二条路径.

Interception

选择路由节点后,路由系统将建立一个特殊的管道来执行该节点. 该管道由选定节点的处理程序和安装到节点中的所有拦截器组成,这些拦截器按从上到下的顺序构成从根到选定节点的路径.

route("/portal") {
   route("articles") {  }
   route("admin") {
      intercept(ApplicationCallPipeline.Features) {  } // verify admin privileges
      route("article/{id}") {  } // manage article with {id}
      route("profile/{id}") {  } // manage profile with {id}
   }
}

给定上面的路由树,当请求URI以/portal/articles开头时,路由将正常处理调用,但是如果请求位于/portal/admin部分,它将首先执行拦截器以验证当前用户是否具有足够的特权来访问管理页面.

其他示例可能是将JSON序列化安装到/api部分,从/user/{id}部分的数据库中加载用户并将其放入调用的属性等.

Extensibility

ktor-server-core模块包含许多基本的选择器,以匹配方法,路径,标题和查询参数,但是您可以轻松添加自己的选择器以适应更复杂的逻辑. 实现RouteSelector并创建一个类似于内置的构建器功能.

路径解析不可扩展.

Tracing the routing decisions

如果您在试图弄清楚为什么不执行路由时遇到问题,Ktor会在路由功能内提供trace方法.

routing {
    trace { application.log.trace(it.buildText()) }
}

每当调用完成后,您便会执行该方法,以跟踪执行的决策. 例如,对于此路由配置:

routing {
    trace { application.log.trace(it.buildText()) }
    get("/bar") { call.respond("/bar") }
    get("/baz") { call.respond("/baz") }
    get("/baz/x") { call.respond("/baz/x") }
    get("/baz/x/{optional?}") { call.respond("/baz/x/{optional?}") }
    get("/baz/{y}") { call.respond("/baz/{y}") }
    get("/baz/{y}/value") { call.respond("/baz/{y}/value") }
    get("/{param}") { call.respond("/{param}") }
    get("/{param}/x") { call.respond("/{param}/x") }
    get("/{param}/x/z") { call.respond("/{param}/x/z") }
    get("/*/extra") { call.respond("/*/extra") }

}

如果请求/bar ,则输出为:

Trace for [bar]
/, segment:0 -> SUCCESS @ /bar/(method:GET))
  /bar, segment:1 -> SUCCESS @ /bar/(method:GET))
    /bar/(method:GET), segment:1 -> SUCCESS @ /bar/(method:GET))
  /baz, segment:0 -> FAILURE "Selector didn't match" @ /baz)
  /{param}, segment:0 -> FAILURE "Better match was already found" @ /{param})
  /*, segment:0 -> FAILURE "Better match was already found" @ /*)

投入生产时,请记住删除或禁用此功能.

by  ICOPY.SITE