Ktorの使い方

時間もできたのでKtorを触ってみることにした。

Ktor

Ktorはアプリケーションフレームワークで、実装に必須なものは modlue と呼ばれる拡張関数(Application.() -> Unit)だけ。

モジュールはFuture(フレームワークでいうミドルウェアみたいなもの)を install() することでパイプラインを作りあげアプリケーションとなる。雰囲気はこんな感じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun Application.mainModule() {
    install(Routing) {
        get("/") {
            if (call.request.local.port == 8080) {
                call.respondText("Connected to public api")
            } else {
                call.respondText("Connected to private api")
            }
        }
    }
}

モジュールの読み込みを設定ファイル(application.conf)で切り替えられる。 この設定ファイルはsrc/main/resourcesに配置しHOCONフォーマットを使う。

設定ファイルの中で${HOME}のように環境変数を参照でき、さらにコマンドラインオプション(-config)で設定ファイルを変更できるので柔軟に設定を切り替えられる。

利用するアプリケーションサーバーは色々と選べるNOTEに書かれてるが下のどれかをmainClassに設定すればアプリケーションサーバーの設定が読み込まれて実行される。

  • io.ktor.server.cio.DevelopmentEngine
  • io.ktor.server.tomcat.DevelopmentEngine
  • io.ktor.server.jetty.DevelopmentEngine
  • io.ktor.server.netty.DevelopmentEngine

Gradleのクイックスタートも準備されている。 本格的なデプロイについてはdeployのページがあり、 Docker,Jetty,Heroku,GAE と解説があるので読めば何とかなりそう。

設定ファイルを使わないで利用してみる

HOCON使って DI できるよみたいなことを書いたけど、設定ファイルを使わずに素朴にmainClassから読み込んで使ってみる。

ビルドスクリプト(build.gradle)

ここではアプリケーションエンジンにio.ktor:ktor-server-nettyを指定している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
buildscript {
    ext.kotlin_version = '1.2.41'
    ext.java_version   = 1.8
    ext.ktor_version = '0.9.2'

    repositories {
        mavenCentral()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "gradle.plugin.org.jmailen.gradle:kotlinter-gradle:1.7.0"
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
    }
}

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.2.21"
    id "com.github.johnrengelman.shadow" version "2.0.2"
}

apply plugin: "kotlin"
apply plugin: "idea"      // for IntelliJ Idea
apply plugin: "org.jmailen.kotlinter"
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: "application"

group 'me.masu_mi.study'
version '1.0-SNAPSHOT'

apply plugin: 'kotlin'

// dependencies to runtime
tasks.withType(JavaCompile) {
    sourceCompatibility = java_version
    targetCompatibility = java_version
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    // compileKotlin, compileTestKotlin
    kotlinOptions.jvmTarget = java_version
    // ref. https://kotlinlang.org/docs/reference/using-gradle.html
}

kotlin { experimental { coroutines "enable" } }

// dependencies to libs
repositories {
    mavenCentral()
    jcenter()
    maven { url "https://dl.bintray.com/kotlin/ktor" }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: kotlin_version
    compile group: "org.jetbrains.kotlin", name: "kotlin-stdlib", version: kotlin_version

    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:1.2.1"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

// configuration of tasks
mainClassName = "me.masu_mi.study.sample_ktor.WebApp"
shadowJar {
    manifest {
        attributes "Main-Class": mainClassName
    }
}
run {
    if (project.hasProperty("args")) {
        args project.args.split("\\s+")
    }
}

コード(kotlin)

ここではembeddedServerNettyを渡して利用している。

Kotlin では関数の引数の最後が関数の場合には () の外に {} を続けて書く略記が認められている。必須とされるモジュールを無名の拡張関数として定義して embeddedServer に渡している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package me.masu_mi.study.sample_ktor

import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch

class WebApp() {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            println("hello")
            val server = embeddedServer(Netty, 8889) {
                routing { // aliase to install(Routing) { ... }
                    get("/") {
                        call.respondText("Hello World!", ContentType.Text.Plain)
                    }
                    get("/demo") {
                        launch {
                            call.respondText("Hello da demo!!")
                        }.join()
                    }
                    get("/foo") {
                        var fromCoroutine: String = ""
                        val v = async {
                            fromCoroutine = "from async"
                            "out"
                        }.await()
                        call.respondText(fromCoroutine + v)
                    }
                }
            }
            server.start(wait = true)
        }
    }
}

コメントにも書いたけど routing { ... }install(Routing) { ... } の略記になっている。 Ktor の根幹をなすinstall()関数の定義は拡張関数のジェネリクスでRoutingなどパイプラインクラスの中で呼ばれている(P = Routing)。Routingはフィーチャーでありパイプラインでもある。

1
2
3
4
fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> P.install(
    feature: ApplicationFeature<P, B, F>,
    configure: B.() -> Unit = {}
): F { /* ... */ }

設定ファイルを使って利用してみる

build.gradle でアプリケーションエンジンを設定して mainClass を変更する。 そして以下の設定ファイルを準備する。

ktor {
    deployment {
        port = 8080
    }

    application {
        modules = [ me.masu_mi.study.sample_ktor.WebApp.mainModule ]
    }
}

moduleMain()関数は最初に書いたものにする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun Application.mainModule() {
    install(Routing) {
        get("/") {
            if (call.request.local.port == 8080) {
                call.respondText("Connected to public api")
            } else {
                call.respondText("Connected to private api")
            }
        }
    }
}
comments powered by Disqus