時間もできたので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)
ここではembeddedServer
にNetty
を渡して利用している。
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")
}
}
}
}
|