Kotlinでktorをちょろっと使ってみた

# Introduction

Kotlin勉強シリーズ。今回はktor。

前回までのKotlinシリーズはこちら。

* [KotlinとGradleとSpekとIntelliJIdeaで環境構築](https://qiita.com/dynamitecoolguy/items/e106948b59ec2e32f291)
* [KotlinでReactorのハンズオン書いてみた](https://qiita.com/dynamitecoolguy/items/f8d61edb4587db0773df)
* [KotlinでProtocol Buffersを使ってみた](https://qiita.com/dynamitecoolguy/items/619d8b22ab257c180976)
* [Kotlinでlettuceを使ってみた](https://qiita.com/dynamitecoolguy/items/015e90cc61edb6699be3)

## バージョン

本記事作成に用いたOS、言語、ミドルウェア等のバージョンは以下の通りです。
なお、JUnitを5.3.1にするとSpekがうまく動作しませんでした。

|種類|名前|バージョン|
|---|---|---|
|OS|Ubuntu|18.04(Bionic Beaver)|
|開発環境|Intellij IDEA|ULTIMATE 2018.2|
|開発言語|Kotlin|1.2.61|
|JVM|OpenJDK|10.0.1|
|ビルド|Gradle|4.10|
|テスト環境1|JUnit|5.2|
|テスト環境2|Spek|1.2.0|
|依存ライブラリ|Ktor|0.9.4|

# 環境設定

## Gradle各種設定

ビルドに使用したGradle用の設定です。

### gradle.properties

[環境構築の記事](https://qiita.com/dynamitecoolguy/items/e106948b59ec2e32f291#gradleproperties)の際に作成したものと同じです。　

```gradle.properties
org.gradle.jvmargs=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit -Dorg.gradle.daemon=false
```

### settings.gradle

```settings.gradle
pluginManagement {
    repositories {
        mavenCentral()

        // Kotlin
        maven {
            url { 'https://dl.bintray.com/kotlin/kotlin-dev' }
        }
    }
}

rootProject.name = 'ktorwork'
```

### build.gradle

Ktorを使用するためのgradleです。
gradle runでアプリを実行するためのApplicationプラグインと、その際のクラス名としてio.ktor.server.netty.DevelopmentEngineを指定しています。
また、ktorでは、Kotlinのexperimentalなcoroutine機能を使用しているので、それを有効にしています(dependencyとkotlin.experimental.coroutines設定)。

```
plugins {
    // Kotlin
    id 'org.jetbrains.kotlin.jvm' version '1.2.61'

    // Application
    id 'application'
}

group 'hoge.hoge'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()

    // Spek
    maven {
        url { 'https://dl.bintray.com/spekframework/spek' }
    }

    // Ktor
    maven {
        url { 'https://dl.bintray.com/kotlin/ktor' }
    }
    maven {
        url { 'https://repo.spring.io/plugins-release' }
    }
}

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

    // JUnit5
    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5:1.2.61'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.2.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.2.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.2.0'

    // Spek
    testImplementation 'org.jetbrains.spek:spek-api:1.2.0'
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.2.61'
    testRuntimeOnly 'org.jetbrains.spek:spek-junit-platform-engine:1.2.0'

    // Ktor
    implementation 'io.ktor:ktor-server-netty:0.9.4'
    implementation 'ch.qos.logback:logback-classic:1.2.3'
    implementation 'org.jetbrains.kotlinx:kotlinx-io-jvm:0.0.10'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.25.0'
    implementation 'io.ktor:ktor-server-test-host:0.9.4'
}

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

kotlin {
    experimental { coroutines 'enable' }
}

test {
    useJUnitPlatform()
}

mainClassName = 'io.ktor.server.netty.DevelopmentEngine'
```

# ソースファイル

## メインソース

ktorのドキュメントにあるサンプルソースより、メインのソースとして以下のコードをsrc/main/kotlin/Application.ktに記述します。
io.ktor.application.Applicationクラスにmodule()メソッドを追加した形です。

```Application.kt
package hoge.hoge

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.route
import io.ktor.routing.routing

fun Application.module() {
    routing {
        route("/snippets") {
            get {
                call.respondText("OK")
            }
        }
    }
}
```

## 設定ファイル

ktorサーバ用の設定ファイルをsrc/main/resources/applicaiton.confに記述します。io.ktor.server.netty.DevelopmentEngineは、この設定ファイルを読み込んでいます。
Kotlinのクラス名には"Kt"postfixが付くので注意です。

```application.conf
ktor.deployment.port = 8080

ktor.application.modules = [hoge.hoge.ApplicationKt.module]
```

## テストソース

Spekテスト用のファイルをsrc/test/kotlin/ApplicationSpec.ktとして記述します。

```ApplicationSpec.kt
package hoge.hoge

import io.ktor.http.HttpMethod
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.createTestEnvironment
import io.ktor.server.testing.handleRequest
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.it
import org.jetbrains.spek.api.dsl.on
import kotlin.test.assertEquals

object ApplicationSpec: Spek({
    describe("Application") {
        val engine = TestApplicationEngine(createTestEnvironment())
        engine.start(wait = false)
        engine.application.module()

        on("SimpleTest") {
            it("says OK") {
                val call = engine.handleRequest(HttpMethod.Get, "/snippets")
                assertEquals("OK", call.response.content)
            }
        }
    }
})
```

# 実行

## Run

IntelliJ IDEAのRun/Debug Configurationsで、Gradle設定を追加し、Gradle projectにこのプロジェクト、Tasksにrunを指定します。
この設定を実行した後、ブラウザで"http://localhost:8080/snippets"にアクセスすると、"OK"が表示されます。

## Test

今度はテスト用の設定を追加します。Runと同様にGradle設定を追加した後、Tasksにtestを指定します。
この設定を実行すると、Spekテストが実行されます。(上記のRunを実行したままだとポート使用中エラーが出るので注意)