[Ktor] Ktor 프로젝트 추가 작업
기본 포트 변경
현재 프로젝트를 실행하면 8080 포트로 접근할 수 있다. 이 포트 번호를 바꾸려면 아래처럼 하면 된다.
먼저 Application.kt 파일을 열어서 아래처럼 수정한다.
import com.example.plugins.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
val env = applicationEngineEnvironment {
envConfig()
}
embeddedServer(Netty, env).start(true)
}
fun ApplicationEngineEnvironmentBuilder.envConfig() {
module {
module()
}
connector {
host = "0.0.0.0"
port = 9292
}
}
fun Application.module() {
configureRouting()
}
connector는 여러 개를 만들 수 있다.
import com.example.plugins.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
val env = applicationEngineEnvironment {
envConfig()
}
embeddedServer(Netty, env).start(true)
}
fun ApplicationEngineEnvironmentBuilder.envConfig() {
module {
module()
}
connector {
host = "0.0.0.0"
port = 9292
}
connector {
host = "127.0.0.1"
port = 9090
}
}
fun Application.module() {
configureRouting()
}
이렇게 수정한후 재실행하면 로그캣에 아래처럼 표시된다.
링크 2개를 각각 클릭하면 각각 다른 도메인으로 접근해서 Hello World!를 표시한다. 링크들을 클릭할 때마다 로그캣에 로그도 정상적으로 표시된다.
새 HTTP 엔드포인트 추가
매번 홈으로만 이동할 순 없다. 도메인 뒤에 다른 글자를 붙여서 다른 페이지로 이동할 수도 있어야 한다.
그러려면 Routing.kt 파일을 수정해야 한다. 이 파일은 com.example 패키지의 plugins 패키지에 들어 있다.
아무것도 수정한 적이 없다면 아래처럼 작성돼 있을 것이다.
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
}
test1을 입력하면 다른 문자열이 보이는 웹 페이지를 열도록 수정한다.
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
get("/test1") {
val text = "<h1>Hello From Ktor</h1>"
val type = ContentType.parse("text/html")
call.respondText(text, type)
}
}
}
ContentType은 임포트해야 하기 때문에 io.ktor.http.*를 임포트한다. 이후 프로젝트를 재실행해서 변경사항이 반영되게 한 다음 127.0.0.1:9292/test1로 접근하면 페이지 상단에 Hello From Ktor가 표시되는 웹 페이지로 이동한다.
static content 구성
static content(정적 컨텐츠)는 서버에 저장돼 있는 데이터다. 그래서 언제 요청을 보내든 항상 동일한 리턴값을 준다. 이런 특징 때문에 정적 컨텐츠는 어떤 유저에게든 동일한 모습으로 보이는데 보통 로고 아이콘이나 이미지 등을 정적 컨텐츠라고 한다.
ktor에선 이 정적 컨텐츠를 어떻게 다루는지 확인한다.
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
staticResources("/content", "mycontent")
get("/") {
call.respondText("Hello World!")
}
}
}
staticResources()를 통해 html, js 파일 같은 표준 웹사이트 컨텐츠를 제공하길 원한다는 걸 ktor에 알린다. 이 컨텐츠는 브라우저 안에서 실행될 수 있지만 서버 관점에선 정적 컨텐츠로 간주된다.
첫 번째 매개변수인 "/content"는 정적 컨텐츠를 요청하기 위한 경로고 "mycontent"는 정적 컨텐츠가 저장될 폴더명이다. 이렇게 설정하면 ktor는 리소스 디렉토리 안에서 myContents란 폴더를 찾는다.
이제 mycontent 폴더를 만들어야 한다. src/main/resources 폴더를 우클릭해서 디렉토리를 새로 만들고 이름을 mycontent로 짓는다. 그리고 이 폴더 안에 sample.html이란 파일을 만들고 아래 코드를 붙여넣는다.
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8" />
<title>정적 컨텐츠 예시</title>
</head>
<body>
<h1>이 페이지는 이걸로 만들어짐 : </h1>
<ol>
<li>Ktor</li>
<li>Kotlin</li>
<li>HTML</li>
</ol>
</body>
</html>
이후 로컬 서버를 재실행해서 위의 변경사항을 적용하고 브라우저 주소창에 127.0.0.1:9090/content/sample.html을 입력하면 아래 페이지가 표시된다.
통합 테스트 작성
ktor 프로젝트엔 통합 테스트 기능이 번들로 제공된다. 테스트 파일은 프로젝트 생성 시 src/test/kotlin/com.패키지명 경로에 ApplicationTest란 이름으로 위치해 있다. 이 파일을 아래처럼 수정한다.
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ApplicationTest {
@Test
fun testRoot() = testApplication {
application {
module()
}
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello World!", response.bodyAsText())
}
}
testApplication 람다는 새 ktor 인스턴스를 만든다. 이 인스턴스는 netty 같은 서버가 아닌 테스트 환경 안에서 실행된다.
그 후 application 람다를 호출해 embeddedServer()에서 호출한 것과 같은 설정을 적용할 수 있다. 이제 어설션 함수를 써서 테스트하려는 부분들을 테스트하면 된다.
Routing.kt에 "/test1" 경로를 추가한 코드까지 모두 포함시킨 다음 테스트 케이스를 하나 추가한다.
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
staticResources("/content", "mycontent")
get("/") {
call.respondText("Hello World!")
}
get("/test1") {
val text = "<h1>Hello From Ktor</h1>"
val type = ContentType.parse("text/html")
call.respondText(text, type)
}
}
}
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
class ApplicationTest {
@Test
fun testRoot() = testApplication {
application {
module()
}
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello World!", response.bodyAsText())
}
@Test
fun testNewEndpoint() = testApplication {
application {
module()
}
val response = client.get("/test1")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("html", response.contentType()?.contentSubtype)
assertContains(response.bodyAsText(), "Hello From Ktor")
}
}
에러 핸들러 등록
StatusPages라는 에러 핸들러는 기본 제공되지 않는 플러그인이기 때문에 의존성을 따로 추가해야 한다.
build.gradle.kts 파일을 열어 아래 의존성을 추가한다. 뒤의 ktor 버전은 내 프로젝트 기준 ktor 버전이다.
implementation("io.ktor:ktor-server-status-pages:$ktor_version") // 2.3.12
그리고 Routing.kt의 configureRouting()을 수정한다. status pages 플러그인을 설치하고 IllegalStateException이 발생 시 어떤 작업을 할지 정하는 게 추가됐다.
fun Application.configureRouting() {
install(StatusPages) {
exception<IllegalStateException> { call, cause ->
call.respondText("App in illegal state as ${cause.message}")
}
}
routing {
staticResources("/content", "mycontent")
get("/") {
call.respondText("Hello World!")
}
get("/test1") {
val text = "<h1>Hello From Ktor</h1>"
val type = ContentType.parse("text/html")
call.respondText(text, type)
}
}
}
그리고 "error-test" 경로로 진입하면 IllegalStateException을 던지게 한다. 최종 코드는 아래와 같다.
fun Application.configureRouting() {
install(StatusPages) {
exception<IllegalStateException> { call, cause ->
call.respondText("App in illegal state as ${cause.message}")
}
}
routing {
staticResources("/content", "mycontent")
get("/") {
call.respondText("Hello World!")
}
get("/test1") {
val text = "<h1>Hello From Ktor</h1>"
val type = ContentType.parse("text/html")
call.respondText(text, type)
}
get("/error-test") {
throw IllegalStateException("지금 너무 바빠요!")
}
}
}
이제 서버를 재실행한 후 127.0.0.1:9090/erorr-test 경로로 진입하면 아래 화면이 표시된다.
참고한 사이트)
https://ktor.io/docs/server-create-a-new-project.html
https://ktor.io/docs/server-requests-and-responses.html