라우팅
라우팅은 서버 애플리케이션에서 들어오는 요청을 처리하기 위한 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는 라우트 핸들러 정의를 훨씬 더 쉽고 간결하게 만들어주는 일련의 함수들을 제공합니다. 예를 들어, 이전 코드를 이제 URL과 요청을 처리할 코드만 필요한 get 함수로 대체할 수 있습니다:
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get("/hello") {
call.respondText("Hello")
}
}
유사하게, Ktor는 put
, post
, head
등 다른 모든 HTTP 동사(verb)에 대한 함수를 제공합니다.
요약하자면, 라우트를 정의하기 위해 다음 설정을 지정해야 합니다:
HTTP 동사(Verb)
GET
,POST
,PUT
등 HTTP 동사를 선택합니다. 가장 편리한 방법은get
,post
,put
등 전용 동사 함수를 사용하는 것입니다.경로 패턴
URL 경로를 일치시키는 데 사용되는 경로 패턴을 지정합니다. 예를 들어,
/hello
,/customer/{id}
와 같습니다.get
/post
/등 함수에 경로 패턴을 바로 전달할 수 있으며, 라우트 핸들러를 그룹화하고 중첩 라우트를 정의하기 위해route
함수를 사용할 수도 있습니다.핸들러
요청과 응답을 처리하는 방법을 지정합니다. 핸들러 내에서
ApplicationCall
에 접근하여, 클라이언트 요청을 처리하고 응답을 보낼 수 있습니다.
경로 패턴 지정
라우팅 함수(route
, get
, post
등)에 전달되는 경로 패턴은 URL의 경로 구성 요소를 일치시키는 데 사용됩니다. 경로는 슬래시(/
) 문자로 구분된 일련의 경로 세그먼트를 포함할 수 있습니다.
참고: Ktor는 후행 슬래시(trailing slash)가 있는 경로와 없는 경로를 구분합니다.
IgnoreTrailingSlash
플러그인을 설치하여 이 동작을 변경할 수 있습니다.
다음은 몇 가지 경로 예시입니다:
/hello
단일 경로 세그먼트를 포함하는 경로입니다./order/shipment
여러 경로 세그먼트를 포함하는 경로입니다. 이러한 경로는 route/get/등 함수에 그대로 전달하거나 여러route
함수를 중첩하여 하위 라우트를 구성할 수 있습니다./user/{login}
login
경로 파라미터가 있는 경로로, 해당 값은 라우트 핸들러 내에서 접근할 수 있습니다./user/*
와일드카드 문자가 있는 경로로, 모든 경로 세그먼트와 일치합니다./user/{...}
테일카드가 있는 경로로, URL 경로의 나머지 전부와 일치합니다./user/{param...}
테일카드를 포함한 경로 파라미터를 포함하는 경로입니다.Regex("/.+/hello")
정규 표현식을 포함하는 경로로,/hello
의 마지막 발생까지의 경로 세그먼트와 일치합니다.
와일드카드 (Wildcard)
와일드카드 (*
)는 모든 경로 세그먼트와 일치하며 생략될 수 없습니다. 예를 들어, /user/*
는 /user/john
과 일치하지만, /user
와는 일치하지 않습니다.
테일카드 (Tailcard)
테일카드 ({...}
)는 URL 경로의 나머지 전부와 일치하며, 여러 경로 세그먼트를 포함할 수 있고, 비어 있을 수도 있습니다. 예를 들어, /user/{...}
는 /user/john/settings
뿐만 아니라 /user
와도 일치합니다.
경로 파라미터 (Path Parameter)
경로 파라미터 ({param}
)는 경로 세그먼트와 일치하고 param
이라는 이름의 파라미터로 캡처합니다. 이 경로 세그먼트는 필수이지만, 물음표를 추가하여 {param?}
선택 사항으로 만들 수 있습니다. 예를 들어:
/user/{login}
은/user/john
과 일치하지만,/user
와는 일치하지 않습니다./user/{login?}
은/user/john
뿐만 아니라/user
와도 일치합니다.참고: 선택적 경로 파라미터
{param?}
는 경로의 끝에만 사용할 수 있습니다.
라우트 핸들러 내에서 파라미터 값에 접근하려면 call.parameters
속성을 사용합니다. 예를 들어, 아래 코드 스니펫에서 /user/admin
경로에 대해 call.parameters["login"]
은 _admin_을 반환합니다:
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}
요청에 쿼리 문자열이 포함된 경우,
call.parameters
에는 이 쿼리 문자열의 파라미터도 포함됩니다. 핸들러 내에서 쿼리 문자열과 해당 파라미터에 접근하는 방법을 알아보려면 쿼리 파라미터를 참조하세요.
테일카드를 포함한 경로 파라미터 (Path Parameter with Tailcard)
테일카드를 포함한 경로 파라미터({param...}
)는 URL 경로의 나머지 전부와 일치하며 각 경로 세그먼트에 대한 여러 값을 param
을 키로 사용하여 파라미터에 넣습니다. 예를 들어, /user/{param...}
는 /user/john/settings
와 일치합니다. 라우트 핸들러 내에서 경로 세그먼트 값에 접근하려면 call.parameters.getAll("param")
을 사용합니다. 위 예시의 경우, getAll
함수는 _john_과 settings 값을 포함하는 배열을 반환합니다.
정규 표현식 (Regular Expression)
정규 표현식은 모든 라우트 핸들러 정의 함수에서 사용할 수 있습니다: route
, get
, post
등입니다.
정규 표현식에 대해 더 자세히 알아보려면 Kotlin 문서를 참조하세요.
/hello
로 끝나는 모든 경로와 일치하는 라우트를 작성해 보겠습니다.
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex(".+/hello")) {
call.respondText("Hello")
}
}
이 라우트 정의를 통해, /hello
로 끝나는 경로에 대한 모든 들어오는 요청(예를 들어 /foo/hello
, /bar/baz/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
함수의 파라미터로 하위 라우트를 가질 수 있도록 허용합니다. 이는 다른 리소스의 논리적 자식인 리소스를 정의하는 데 유용할 수 있습니다. 다음 예시는 /order/shipment
에 대한 GET
및 POST
요청에 응답하는 방법을 보여줍니다:
routing {
route("/order") {
route("/shipment") {
get {
}
post {
}
}
}
}
따라서 각 route
호출은 별도의 경로 세그먼트를 생성합니다.
라우트 확장 함수
일반적인 패턴은 Route
타입에 대한 확장 함수를 사용하여 실제 라우트를 정의하는 것입니다. 이를 통해 동사(verb)에 쉽게 접근하고 모든 라우트가 단일 라우팅 함수에 있는 복잡함을 없앨 수 있습니다. 이 패턴은 라우트를 그룹화하는 방식과 관계없이 적용할 수 있습니다. 따라서 첫 번째 예시는 더 깔끔한 방식으로 표현될 수 있습니다:
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를 참조하세요.
애플리케이션의 유지 보수성을 고려하여 확장하려면, 특정 구조화 패턴을 따르는 것이 좋습니다.
라우트 추적
로깅이 구성된 경우, Ktor는 일부 라우트가 실행되지 않는 이유를 파악하는 데 도움이 되는 라우트 추적(route tracing)을 활성화합니다. 예를 들어, 애플리케이션을 실행하고 지정된 엔드포인트로 요청을 보내면, 애플리케이션의 출력은 다음과 같을 수 있습니다:
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" @ /
네이티브 서버에서 라우트 추적을 활성화하려면, 애플리케이션을 실행할 때
KTOR_LOG_LEVEL
환경 변수에 TRACE 값을 전달하세요.