Kotlin Ktor의 DSL을 이용한 웹페이지 생성

|

DSL을 이용한 웹페이지 만들기

FreeMaker 모듈 대신 HTML DSL(Domain specific language)를 이용해서 웹페이지를 응답하는 코드 예제입니다.

보다 자세한 내용은 여기에서 볼 수 있습니다.

build.gradle

Ktor용 App을 위한 기본 build.gradle의 종속성에 FreeMaker 종속성 대신에 implementation "io.ktor:ktor-html-builder:$ktor_version" 항목이 추가되었습니다.

repositories 항목에 jcenter()를 추가해야 됩니다.

group 'com.snowdeer'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.3.61'
    ext.ktor_version = '1.2.6'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'

sourceCompatibility = 1.8
application {
    mainClassName = "com.snowdeer.BlogAppKt"
}

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

    implementation "io.ktor:ktor-html-builder:$ktor_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

kotlin {
    experimental {
        coroutines "enable"
    }
}


BlogApp.kt

package com.snowdeer

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.html.respondHtml
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.routing.get
import io.ktor.routing.routing
import kotlinx.html.*

data class IndexData(val items: List<Int>)

fun Application.main() {
    routing {
        static("/static") {
            resources("static")
        }

        get("/") {
            val data = IndexData(listOf(1, 2, 3))
            call.respondHtml {
                head {
                    link(rel = "stylesheet", href = "/static/styles.css")
                }
                body {
                    ul {
                        for (item in data.items) {
                            li { +"$item" }
                        }
                    }
                }
            }
        }
    }
}

Kotlin Ktor 활용한 간단한 Login 페이지 만들기

|

Login 기능 구현하기

login.ftl

resources/templates/login.ftl 파일을 생성합니다.

<html>
<head>
    <link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<#if error??>
    <p style="color:red;">${error}</p>
</#if>
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
    <div>User:</div>
    <div><input type="text" name="username" /></div>
    <div>Password:</div>
    <div><input type="password" name="password" /></div>
    <div><input type="submit" value="Login" /></div>
</form>
</body>
</html>


Main.kt

package com.snowdeer

import freemarker.cache.ClassTemplateLoader
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.freemarker.FreeMarker
import io.ktor.freemarker.FreeMarkerContent
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.request.receiveParameters
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.route
import io.ktor.routing.routing

fun Application.main() {
    install(FreeMarker) {
        templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
    }

    routing {
        static("/static") {
            resources("static")
        }

        route("/login") {
            get {
                call.respond(FreeMarkerContent("login.ftl", null))
            }
            post {
                val post = call.receiveParameters()

                val username = post["username"]
                println("[snowdeer] username: $username")
                if (username != null && post["password"] != null) {
                    call.respondText("OK")
                } else {
                    call.respond(FreeMarkerContent("login.ftl", mapOf("error" to "Invalid login")))
                }
            }
        }
    }

}

만약 리다이렉션(Redirection)을 하고 싶으면 다음과 같은 코드를 사용하면 됩니다.

call.respondRedirect("/", permanent = false)

Kotlin Ktor 활용한 간단한 웹페이지(index.html)에 CSS 적용하기

|

CSS 파일 적용하기

앞서 만들었던 FreeMaker 템플릿 기반 샘플 예제에서, css 파일을 적용하려면 다음과 같이 수정합니다.

BlogApp.kt

먼저 routing 정보에 static 정보를 포함하도록 선언합니다.

package com.snowdeer

import freemarker.cache.ClassTemplateLoader
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.freemarker.FreeMarker
import io.ktor.freemarker.FreeMarkerContent
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.routing

data class IndexData(val items: List<Int>)

fun Application.main() {
    install(FreeMarker) {
        templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
    }

    routing {
        static("/static") {
            resources("static")
        }

        get("/html-freemarker") {
            call.respond(FreeMarkerContent("index.ftl", mapOf("data" to IndexData(listOf(1, 2, 3))), ""))
        }
    }
}


css 파일 추가

resources/static/styles.css 파일을 추가합니다.

body {
    background: #B9D8FF;
}


index.ftl 수정

resources/templates/index.ftlcss 파일을 불러오는 코드를 추가합니다.

<html>
    <head>
        <link rel="stylesheet" href="/static/styles.css">
    </head>

	<body>
		<ul>
		<#list data.items as item>
			<li>${item}</li>
		</#list>
		</ul>
	</body>
</html>



그 외

위에서 사용했던 static 코드는 텍스트 파일만 추가하는 것이 아니라 이미지 파일이나 기타 다른 파일들도 불러올 수 있습니다. <img src="..."> 태그를 이용해서 자유롭게 이미지를 삽입할 수 있습니다.

Kotlin Ktor 활용한 간단한 웹페이지(index.html) 띄우기 (FreeMarker 활용)

|

build.gradle

Ktor용 App을 위한 기본 build.gradle의 종속성에 implementation "io.ktor:ktor-freemarker:$ktor_version" 항목이 추가되었습니다.

group 'com.snowdeer'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.3.61'
    ext.ktor_version = '1.2.6'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'

sourceCompatibility = 1.8
application {
    mainClassName = "com.snowdeer.BlogAppKt"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

    implementation "io.ktor:ktor-freemarker:$ktor_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

kotlin {
    experimental {
        coroutines "enable"
    }
}


index.ftl

resources/templates/index.ftl에 템플릿 파일을 추가합니다.

<html>
	<body>
		<ul>
		<#list data.items as item>
			<li>${item}</li>
		</#list>
		</ul>
	</body>
</html>


BlogApp.kt

package com.snowdeer

import freemarker.cache.ClassTemplateLoader
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.freemarker.FreeMarker
import io.ktor.freemarker.FreeMarkerContent
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.routing

data class IndexData(val items: List<Int>)

fun Application.main() {
    install(FreeMarker) {
        templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
    }

    routing {
        get("/html-freemarker") {
            call.respond(FreeMarkerContent("index.ftl", mapOf("data" to IndexData(listOf(1, 2, 3))), ""))
        }
    }
}

Kotlin Simple Web Server (Ktor 활용)

|

build.gradle

group 'com.snowdeer'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.3.61'
    ext.ktor_version = '1.2.6'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'java'
apply plugin: 'kotlin'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    compile "io.ktor:ktor-server-netty:$ktor_version"

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

kotlin {
    experimental {
        coroutines "enable"
    }
}


BlogApp.kt

package com.snowdeer

import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*

fun main(args: Array<String>) {
    embeddedServer(Netty, 8080) {
        routing {
            get("/") {
                call.respondText("SnowDeer's Blog", ContentType.Text.Html)
            }
        }
    }.start(wait = true)
}


Application 모듈을 활용한 BlogApp.kt

package com.snowdeer

import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*

fun Application.module() {
    install(DefaultHeaders)
    install(CallLogging)
    install(Routing) {
        get("/") {
            call.respondText("SnowDeer Blog2", ContentType.Text.Html)
        }
    }
}

fun main(args: Array<String>) {
    embeddedServer(Netty, 8080, watchPaths = listOf("BlogAppKt"), module = Application::module).start()
}


application.conf 사용해서 환경 변수 분리하기

main/resources/ 디렉토리 아래에 application.conf에 파일을 만들고 다음 내용을 작성합니다.

ktor {
    deployment {
        port = 8070
    }

    application {
        modules = [ com.snowdeer.BlogAppKt.main ]
    }
}

적용 확인을 위해서 포트 번호를 8070으로 했습니다.

그 이후 BlogApp.kt 파일은 다음과 같이 변경해줍니다.

package com.snowdeer

import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*

fun Application.main() {
    install(DefaultHeaders)
    install(CallLogging)
    install(Routing) {
        get("/") {
            call.respondText("SnowDeer Blog2", ContentType.Text.Html)
        }
    }
}

기존 코드에서 main 전역 메소드가 사라졌고, fun Application.module() 메소드가 fun Application.main()로 변경되었습니다.

또한 build.gradle에 다음 내용을 추가합니다.

apply plugin: 'application'

mainClassName = "com.snowdeer.BlogAppKt"

마지막으로 IntelliJ IDE에서 Run -> Edit Configurations 메뉴로 가서 Main Classio.ktor.server.netty.EngineMain으로 해주고 실행하면 위에서 작성한 application.conf 환경 변수가 적용되어 실행되는 것을 확인할 수 있습니다.