KtorでJSONを返すまで

昨日、[KtorのLocationsの使い方](https://qiita.com/KissyBnts/items/86b08495ad924ae98d32)という記事を投稿したのですが、`Ktor`のタグがついた記事が無かったのでHello world的なものを投稿しておきます。

Kotlin製のWeb frameworkである`Ktor`のプロジェクトの作成からJSONを返すところまでを書こうと思います。
だいたい[公式](http://ktor.io/quickstart/index.html)に書いてありますが。
`Kotlin`のVersionは`1.1.51`、`Ktor`のVersionは`0.9.0`です。

__2017/11/29追記__
`Kotlin`のVersionを`1.2.0`にあげても普通に動きました。

ビルドツールは`Gradle`、IDEは`IntelliJ IDEA`を使います。

# Hello world
## 目次
1. プロジェクトの作成
2. `build.gradle`の追記
3. `Application.kt`の作成と編集
4. 設定ファイルの作成と編集
4. Run

## 1. プロジェクトの作成

IntelliJ IDEAで create new projectから`Gradle`を選択し、`Java`と`Kotlin(Java)`にチェックを入れてプロジェクト作成まで進めます。
あとは適切な名前をつけてUse auto-importにチェックを入れるぐらいです。

## 2. `build.gradle`の追記

生成されたプロジェクトの`build.gradle`を編集していきます。
初期状態はこんな感じだと思います。

```groovy:build.gradle
group 'Example'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.51'

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

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

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
```

やることとしては

1. `Ktor`関連に必要な`repository`と`dependency`を追加
2. `application` pluginの追加

です。

### 2-1 `Ktor`関連に必要な`repository`と`dependency`を追加

まず`Ktor`のバージョンを定義しておきましょう。

```groovy:build.gradle
buildscript {
    ext {
        kotlin_version = '1.1.51'
        ktor_version = '0.9.0'
    }
    ...
}
```

次に必要な`repository`を追加します。
`Ktor`と`Ktor`が依存している`Kotlin`の`coroutine`関連はbintray上にあるのでそれらを追加します。

```groovy:build.gradle
repositories {
    mavenCentral()
    // 以下2つを追加👇
    maven { url  "http://dl.bintray.com/kotlin/ktor" }
    maven { url "https://dl.bintray.com/kotlin/kotlinx" }
}
```

これで`dependency`を追加する準備が整いました。
`Ktor`と`Netty`、ログ出力のために`logback`を追加します。
あとで使うのでJSONパーサーの`jackson`も追加しておきます。
`jackson`については、`Ktor`の`Feature`として提供されているものを使用します。
これには`jackson-module-kotlin`が同梱されています。

```groovy:build.gradle
dependencies {
    // Kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

    // Ktor 以下3つを追加👇
    compile "io.ktor:ktor-server-core:$ktor_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "io.ktor:ktor-jackson:$ktor_version"

    // Log 追加👇
    compile "ch.qos.logback:logback-classic:1.2.1"

    // Testing
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
```

`Kotlin`の`coroutine`はまだexperimental(実験的な)機能のため、利用可能にするために設定が必要ですので以下のコードを追加しましょう。

```groovy:build.gradle
kotlin {
    experimental {
        coroutines "enable"
    }
}
```


### 2-2 `application` pluginの追加

[2-1](#2-1-Ktor関連に必要なrepositoryとdependencyを追加)だけでも`Ktor`を動かすことはできますが、`application`pluginの追加もしてしまいましょう。

```groovy:build.gradle
apply plugin: 'java'
apply plugin: 'kotlin'
 // 追加👇
apply plugin: 'application'
```

併せて`mainClassName`を指定します。この時に、`io.ktor.server.netty.DevelopmentEngine`を指定します。

```groovy:build.gradle
mainClassName = 'io.ktor.server.netty.DevelopmentEngine'
```

これで`build.gradle`の編集は終了です。

最終的にはこんな感じになります。

```groovy:build.gradle
group 'Example'
version '1.0-SNAPSHOT'

buildscript {
    ext {
        kotlin_version = '1.1.51'
        ktor_version = '0.9.0'
    }

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

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

sourceCompatibility = 1.8

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

repositories {
    mavenCentral()
    maven { url  "http://dl.bintray.com/kotlin/ktor" }
    maven { url "https://dl.bintray.com/kotlin/kotlinx" }
}

dependencies {
    // Kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

    // Ktor
    compile "io.ktor:ktor-server-core:$ktor_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "io.ktor:ktor-jackson:$ktor_version"

    // Log
    compile "ch.qos.logback:logback-classic:1.2.1"

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

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

kotlin {
    experimental {
        coroutines "enable"
    }
}
```

## 3. `Application.kt`の作成と編集

`main`関数を記述するファイルを作成します。

```kotlin:Application.kt
package example

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
import io.ktor.response.respond
import io.ktor.routing.get

// 1. io.ktor.application.Applicationの拡張関数としてmain()を定義
fun Application.main() {
    // 2. Loggingの機能をinstall
    install(CallLogging)
    // 3. Routingの設定
    install(Routing) {
        get("/hello") {
            call.respond("Hello world from Ktor!")
        }
    }
}
```

1. io.ktor.application.Applicationの拡張関数としてmain()を定義
`fun main(args: Array<String>) {}`ではなく、`Application.main() {}`で定義し、ここにアプリケーションの設定などを書いていきます。

2. Loggingの機能をinstall
[`build.gradle`の追記](#build.gradleの追記)で`logback`追加しましたがそれを利用するために`CallLogging feature`をinstallします。
`Ktor`ではこのように必要な機能を`Feature`という形で提供しており、それぞれinstallして利用します。

3. Routingの設定
`Routing`も`Feature`ですので、installします。そして、その中にルーティングと処理を記載します。
今回は`/hello`にアクセスすると`Hello world from Ktor!`という文字列を返すように記述しています。

`Application.kt`に関してはこれで終わりです。

## 4. 設定ファイルの作成と編集

アプリケーションの設定ファイル`application.conf`と`logback`の設定ファイル`logback.xml`を作成します。
どちらも`/src/main/resources`直下に配置します。

```conf:application.conf
ktor {
  deployment {
    port = 8080
  }
  application {
    modules = [ example.ApplicationKt.main ]
  }
}
```
`application.conf`はPlay frameworkでも採用されている`Typesafe Config`を使用して記述します。
`deployment.port`で起動するアプリケーションのポートを、`application.modules`で`main`関数を指定します。

```xml:logback.xml
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="trace">
        <appender-ref ref="STDOUT"/>
    </root>

    <logger name="org.eclipse.jetty" level="INFO"/>
    <logger name="io.netty" level="INFO"/>
</configuration>
```
[公式](http://ktor.io/quickstart/gradle.html)のものをコピペしました。お好みにカスタマイズしてください。


## 4. Run

ようやくHello worldする準備が整いました。
`$ gradle run`やIntelliJ IDEAにRun/Debug configurationを追加して実行ができます。

Run/Debug configurationは`Main class`と`Use classpath of module`を設定するだけです。
`Use classpath of module`は作成したプロジェクト名のものに読み替えてください。
<img width="1070" alt="スクリーンショット 2017-11-24 15.41.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/86504/f298a0de-a57d-3c09-858c-5ca3d19d8f7a.png">

`http://localhost:8080`にアクセスして、Hello worldしましょう。

# JSONを返す

Hello worldだけしてもあれなので、JSONを返すようにします。

## 目次

1. `Jackson feature`をinstall
2. data classを返すエンドポイントを追加
3. Run
4. `Jackson Datatype`の追加例

## 1. `Jackson feature`をinstall

[上記](#2-1-Ktor関連に必要なrepositoryとdependencyを追加)で`jackson`を`dependency`に追加しているので、`Application.main()`でinstallするだけで利用可能になります。

```kotlin:Application.kt

// 追加するimportだけ記載しています
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.features.ContentNegotiation
import io.ktor.jackson.jackson

fun Application.main() {
    // ...

    install(ContentNegotiation) {
        jackson {
            configure(SerializationFeature.INDENT_OUTPUT, true)
        }
    }

    install(Routing) {
        // ...
    }
}
```

`install(ContentNegotiation)`の中で`jackson`の設定を行います。
出力されるJSONがインデントされた状態になるようにだけ設定をしています。

## 2. `data class`を返すエンドポイントを追加

続いてエンドポイントを追加します。ここでは`/item`とします。
返すのはなんでもいいのですが、お手軽なので簡単な`Item`という`data class`を定義して返すようにします。

```kotlin:Application.kt
fun Application.main() {
    // ...

    install(Routing) {
        // ...

        get("/item") {
            call.respond(Item(1, "Hoge"))
        }
    }
}

data class Item(val id: Int, val name: String)
```

実装はこれだけです。

## 3. Run

アプリケーションを起動して、`http://localhost:8080/item`にアクセスすると以下のJSONが返却されると思います。

```json
{
  "id": 1,
  "name": "Hoge"
}
```

## 4. `Jackson Datatype`の追加例

Jacksonには非標準の型のSerializer/Deserializerに対応するために追加モジュールが用意されています。
今回はJSR-310日付型に対応する`jackson-datatype-jsr310`モジュールを追加して`LocalDate`型のデフォルトSerializer/Deserializerを設定してみます。

1. `dependency`の追加
2. `Application.kt`の編集
3. Run

### 1. `dependency`の追加

依存関係を`build.gradle`に追加します。

```groovy:build.gradle
dependencies {
    // ...

    compile ('com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.2')

    // ...
}
```

2. `Application.kt`の編集

`jackson`の設定と、`Item`クラスに`LocalDate`型のフィールドを追加します。
ここでは、デフォルトで`"yyyy/MM/dd"`形式でSerializer/Deserializerされるように設定しています。
スコープ関数の`apply`でスッキリ書けますね。

```kotlin:Application.kt

// 追加するimportだけ記載しています
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer
import java.time.LocalDate
import java.time.format.DateTimeFormatter

fun Application.main() {
    // ...

    install(ContentNegotiation) {
        jackson {
            configure(SerializationFeature.INDENT_OUTPUT, true)
            registerModule(JavaTimeModule().apply {
                addSerializer(LocalDate::class.java, LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy/MM/dd")))
                addDeserializer(LocalDate::class.java, LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy/MM/dd")))
            })
        }
    }

    install(Routing) {
        // ...

        get("/item") {
            call.respond(Item(1, "Hoge", LocalDate.now()))
        }
    }
}

data class Item(val id: Int, val name: String, val date: LocalDate)
```

## 3. Run

アプリケーションを起動して、`http://localhost:8080/item`にアクセスすると以下のJSONが返却されると思います。

```json
{
  "id": 1,
  "name": "Hoge",
  "date": "2017/11/24"
}
```

- - - - -

もっと簡単にHello worldする方法もあるけど、外だしできる設定は外出しした方がいい気がするので、現状これでいいんじゃないかな。