라우팅
라우팅(Routing)은 서버 애플리케이션에서 들어오는 요청을 처리하기 위한 핵심 Ktor 플러그인입니다. 클라이언트가 특정 URL(예: /hello)로 요청을 보낼 때, 라우팅 메커니즘을 통해 이 요청을 어떻게 처리할지 정의할 수 있습니다.
라우팅 설치
라우팅 플러그인은 다음과 같은 방식으로 설치할 수 있습니다:
import io.ktor.server.routing.*
install(RoutingRoot) {
// ...
}라우팅 플러그인은 모든 애플리케이션에서 매우 흔하게 사용되므로, 라우팅 설치를 더 간단하게 만들어 주는 편리한 routing 함수가 제공됩니다. 아래 코드 스니펫에서는 install(RoutingRoot)이 routing 함수로 대체되었습니다:
import io.ktor.server.routing.*
routing {
// ...
}라우트 핸들러 정의
라우팅 플러그인을 설치한 후, routing 내부에서 route 함수를 호출하여 라우트를 정의할 수 있습니다:
import io.ktor.server.routing.*
import io.ktor.http.*
import io.ktor.server.response.*
routing {
route("/hello", HttpMethod.Get) {
handle {
call.respondText("Hello")
}
}
}Ktor는 라우트 핸들러를 훨씬 쉽고 간결하게 정의할 수 있는 일련의 함수들도 제공합니다. 예를 들어, 위의 코드를 get 함수로 대체할 수 있으며, 이제 URL과 요청을 처리할 코드만 전달하면 됩니다:
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get("/hello") {
call.respondText("Hello")
}
}마찬가지로 Ktor는 put, post, head 등 다른 모든 메서드(verb)에 대한 함수를 제공합니다.
요약하자면, 라우트를 정의하기 위해 다음과 같은 설정을 지정해야 합니다:
HTTP 메서드(HTTP verb)
GET,POST,PUT등과 같은 HTTP 메서드를 선택합니다. 가장 편리한 방법은get,post,put등과 같은 전용 메서드 함수를 사용하는 것입니다.경로 패턴(Path pattern)
URL 경로를 매칭하는 데 사용할 경로 패턴을 지정합니다 (예:
/hello,/customer/{id}). 경로 패턴을get/post등의 함수에 직접 전달하거나,route함수를 사용하여 라우트 핸들러를 그룹화하고 중첩된 라우트를 정의할 수 있습니다.핸들러(Handler)
요청(requests) 및 응답(responses)을 처리하는 방법을 지정합니다. 핸들러 내부에서
ApplicationCall에 접근하여 클라이언트 요청을 처리하고 응답을 보낼 수 있습니다.
경로 패턴 지정
라우팅 함수(route, get, post 등)에 전달된 경로 패턴은 URL의 경로(path) 구성 요소를 매칭하는 데 사용됩니다. 경로는 슬래시 / 문자로 구분된 일련의 경로 세그먼트(path segments)를 포함할 수 있습니다.
Ktor는 후행 슬래시(trailing slash)가 있는 경로와 없는 경로를 구분합니다.
IgnoreTrailingSlash플러그인을 설치하여 이 동작을 변경할 수 있습니다.
아래는 몇 가지 경로 예시입니다:
/hello
단일 경로 세그먼트를 포함하는 경로입니다./order/shipment
여러 경로 세그먼트를 포함하는 경로입니다. 이러한 경로를 route/get/etc. 함수에 그대로 전달하거나, 여러route함수를 중첩(nesting)하여 하위 라우트를 구성할 수 있습니다./user/{login}login경로 파라미터(path parameter)가 포함된 경로로, 라우트 핸들러 내부에서 해당 값에 접근할 수 있습니다./user/*
모든 경로 세그먼트와 매칭되는 와일드카드(wildcard) 문자가 포함된 경로입니다./user/{...}
URL 경로의 나머지 모든 부분과 매칭되는 테일카드(tailcard)가 포함된 경로입니다./user/{param...}
테일카드가 포함된 경로 파라미터가 있는 경로입니다.Regex("/.+/hello")
마지막으로 나타나는/hello까지의 경로 세그먼트와 매칭되는 정규 표현식(regular expression)이 포함된 경로입니다.
와일드카드
와일드카드 (*)는 모든 경로 세그먼트와 매칭되며 생략될 수 없습니다. 예를 들어, /user/*는 /user/john과는 매칭되지만, /user와는 매칭되지 않습니다.
테일카드
테일카드 ({...})는 URL 경로의 나머지 모든 부분과 매칭되며, 여러 경로 세그먼트를 포함할 수 있고 비어 있을 수도 있습니다. 예를 들어, /user/{...}는 /user/john/settings뿐만 아니라 /user와도 매칭됩니다.
경로 파라미터
경로 파라미터 ({param})는 경로 세그먼트와 매칭되며 이를 param이라는 이름의 파라미터로 캡처합니다. 이 경로 세그먼트는 필수이지만, 물음표를 추가하여 선택 사항으로 만들 수 있습니다: {param?}. 예시:
/user/{login}은/user/john과는 매칭되지만,/user와는 매칭되지 않습니다./user/{login?}은/user/john및/user모두와 매칭됩니다.선택적 경로 파라미터
{param?}는 경로의 끝에서만 사용할 수 있습니다.
라우트 핸들러 내부에서 파라미터 값에 접근하려면 call.parameters 속성을 사용합니다. 예를 들어, 아래 코드 스니펫의 call.parameters["login"]은 /user/admin 경로에 대해 _admin_을 반환합니다:
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}요청에 쿼리 스트링(query string)이 포함된 경우,
call.parameters에는 해당 쿼리 스트링의 파라미터도 포함됩니다. 핸들러 내부에서 쿼리 스트링과 그 파라미터에 접근하는 방법은 쿼리 파라미터를 참조하세요.
테일카드가 포함된 경로 파라미터
테일카드가 포함된 경로 파라미터({param...})는 URL 경로의 나머지 모든 부분과 매칭되며, 각 경로 세그먼트에 대한 여러 값을 param을 키로 사용하여 파라미터에 저장합니다. 예를 들어, /user/{param...}은 /user/john/settings와 매칭됩니다. 라우트 핸들러 내부에서 경로 세그먼트의 값들에 접근하려면 call.parameters.getAll("param")을 사용합니다. 위의 예시에서 getAll 함수는 _john_과 settings 값을 포함하는 배열을 반환합니다.
정규 표현식
정규 표현식은 route, get, post 등 모든 라우트 핸들러 정의 함수와 함께 사용할 수 있습니다.
정규 표현식에 대해 더 자세히 알아보려면 Kotlin 문서를 참조하세요.
/hello로 끝나는 모든 경로와 매칭되는 라우트를 작성해 보겠습니다.
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex(".+/hello")) {
call.respondText("Hello")
}
}이 라우트 정의를 사용하면 /foo/hello, /bar/baz/hello 등 /hello로 끝나는 경로로 들어오는 모든 요청이 매칭됩니다.
핸들러에서 경로 부분에 접근하기
정규 표현식에서 이름이 붙은 그룹(named groups)은 패턴과 매칭되는 문자열의 특정 부분을 캡처하고 이름을 할당하는 방법입니다. (?<name>pattern) 구문은 이름이 붙은 그룹을 정의하는 데 사용되며, 여기서 name은 그룹의 이름이고 pattern은 그룹과 매칭되는 정규 표현식 패턴입니다.
라우트 함수에서 이름이 붙은 그룹을 정의하면 경로의 일부를 캡처할 수 있으며, 핸들러 함수에서는 call.parameters 객체를 사용하여 캡처된 파라미터에 접근할 수 있습니다.
예를 들어, 정수 식별자 뒤에 /hello가 붙은 경로에 대한 요청과 매칭되는 라우트를 정의할 수 있습니다.
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex("""(?<id>\d+)/hello""")) {
val id = call.parameters["id"]!!
call.respondText(id)
}
}위 코드에서 (?<id>\d+)라는 이름의 그룹은 요청된 경로에서 정수 식별자 id를 캡처하는 데 사용되며, call.parameters 속성은 핸들러 함수에서 캡처된 id 파라미터에 접근하는 데 사용됩니다.
이름이 없는 그룹은 정규식 라우트 핸들러 내부에서 접근할 수 없지만, 경로를 매칭하는 데 사용할 수는 있습니다. 예를 들어, 경로 hello/world는 매칭되지만 hello/World는 매칭되지 않습니다:
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex("hello/([a-z]+)")) {
call.respondText("Hello")
}
}또한, 전체 경로 세그먼트가 정규식에 의해 소비되어야 합니다. 예를 들어, 경로 패턴 get(Regex("[a-z]+"))는 경로 "hello1"과는 매칭되지 않지만, 경로 hello/1의 hello 부분과는 매칭되고 /1은 다음 라우트를 위해 남겨둡니다.
여러 라우트 핸들러 정의
메서드 함수별로 라우트 그룹화
모든 애플리케이션이 그렇듯 여러 라우트 핸들러를 정의하려는 경우, routing 함수에 추가하기만 하면 됩니다:
routing {
get("/customer/{id}") {
}
post("/customer") {
}
get("/order") {
}
get("/order/{id}") {
}
}이 경우 각 라우트는 자체 함수를 가지며 특정 엔드포인트와 HTTP 메서드에 응답합니다.
경로별로 라우트 그룹화
대안적인 방법은 경로별로 그룹화하는 것입니다. 경로를 정의한 다음 route 함수를 사용하여 해당 경로에 대한 메서드들을 중첩된 함수로 배치합니다:
routing {
route("/customer") {
get {
}
post {
}
}
route("/order") {
get {
}
get("/{id}") {
}
}
}중첩된 라우트
그룹화 방식과 관계없이, Ktor는 route 함수의 파라미터로 하위 라우트(sub-routes)를 가질 수 있도록 허용합니다. 이는 논리적으로 다른 리소스의 자식인 리소스를 정의할 때 유용할 수 있습니다. 다음 예시는 /order/shipment에 대한 GET 및 POST 요청에 응답하는 방법을 보여줍니다:
routing {
route("/order") {
route("/shipment") {
get {
}
post {
}
}
}
}따라서 각 route 호출은 별도의 경로 세그먼트를 생성합니다.
라우팅 함수(route, get, post 등)에 전달된 경로 패턴은 URL의 경로(path) 구성 요소를 매칭하는 데 사용됩니다. 경로는 슬래시 / 문자로 구분된 일련의 경로 세그먼트를 포함할 수 있습니다.
라우트 확장 함수
일반적인 패턴은 Route 타입에 대한 확장 함수를 사용하여 실제 라우트를 정의하는 것입니다. 이를 통해 메서드에 쉽게 접근할 수 있고 단일 라우팅 함수에 모든 라우트가 모여 있어 발생하는 복잡함을 제거할 수 있습니다. 이 패턴은 라우트를 어떻게 그룹화하기로 결정했는지와 관계없이 적용할 수 있습니다. 따라서 첫 번째 예시를 다음과 같이 더 깔끔하게 표현할 수 있습니다:
routing {
listOrdersRoute()
getOrderRoute()
totalizeOrderRoute()
}
fun Route.listOrdersRoute() {
get("/order") {
}
}
fun Route.getOrderRoute() {
get("/order/{id}") {
}
}
fun Route.totalizeOrderRoute() {
get("/order/{id}/total") {
}
}이 접근 방식을 보여주는 전체 예시는 legacy-interactive-website를 참조하세요.
유지보수 측면에서 애플리케이션의 규모를 확장하려면 특정 구조화 패턴(structuring patterns)을 따르는 것이 권장됩니다.
라우트 추적
로깅(logging)이 설정된 경우, Ktor는 일부 라우트가 실행되지 않는 이유를 파악하는 데 도움이 되는 라우트 추적(route tracing) 기능을 제공합니다. 예를 들어, 애플리케이션을 실행(run)하고 지정된 엔드포인트로 요청을 보내면 애플리케이션의 출력이 다음과 같이 보일 수 있습니다:
TRACE Application - Trace for [missing-page]
/, segment:0 -> SUCCESS @ /
/, segment:0 -> SUCCESS @ /
/ [(method:GET)], segment:0 -> FAILURE "Not all segments matched" @ / [(method:GET)]
Matched routes:
No results
Route resolve result:
FAILURE "No matched subtrees found" @ /Native 서버에서 라우트 추적을 활성화하려면, 애플리케이션을 실행할 때
KTOR_LOG_LEVEL환경 변수에 TRACE 값을 전달하세요.
