Home About Contact
Kotlin Ktor , Gradle

最小限の Ktor アプリを用意

Gradle 8.12 で JSON を返すだけのサーバーを書く。

$ gradle --version
Welcome to Gradle 8.12!

gradle init する:

$ gradle init
Starting a Gradle Daemon (subsequent builds will be faster)

Select type of build to generate:
  1: Application
  2: Library
  3: Gradle plugin
  4: Basic (build structure only)
Enter selection (default: Application) [1..4] 1

Select implementation language:
  1: Java
  2: Kotlin
  3: Groovy
  4: Scala
  5: C++
  6: Swift
Enter selection (default: Java) [1..6] 2

Enter target Java version (min: 7, default: 21): 17

Project name (default: my-ktor): 

Select application structure:
  1: Single application project
  2: Application and library project
Enter selection (default: Single application project) [1..2] 1

Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 1

Select test framework:
  1: kotlin.test
  2: JUnit Jupiter
Enter selection (default: kotlin.test) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] yes

生成された内容:

$ tree .
.
├── app
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   ├── kotlin
│       │   │   └── org
│       │   │       └── example
│       │   │           └── App.kt
│       │   └── resources
│       └── test
│           ├── kotlin
│           │   └── org
│           │       └── example
│           │           └── AppTest.kt
│           └── resources
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts

不要ファイル/ディレクトリの削除:

必要なファイルを追加:

コードを書く

追加したファイルにはそれぞれ次のコードを書く。

application.conf

ktor {
    deployment {
        port = 8080
    }

    application {
        modules = [ org.example.ApplicationKt.main ]
    }
}

Application.kt

@file:Suppress("InvalidPackageDeclaration")
package org.example

import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.ContentType
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Pokemon(val id: Int, val name: String)

fun Application.main() {
    val defaultPokemon = Pokemon(0, "Pikachu")

    val pokemonStorage = mutableListOf<Pokemon>()
    pokemonStorage.addAll(
        arrayOf(
            Pokemon(1, "Squirtle"),
            Pokemon(2, "Charamander"),
            Pokemon(3, "Metapod"),
        )
    )

    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            isLenient = true
        })
    }

    routing {
        get("/") {
            val pokemons: List<Pokemon> = pokemonStorage.toList()
            call.respond(pokemons)
        }

        get("/{id}") {
            val id = call.parameters["id"]
            val pokemon: Pokemon? = pokemonStorage.find { it.id == id!!.toInt() }
            if( pokemon!=null ){
                call.respond(pokemon)
            } else {
                call.respond(defaultPokemon)
            }
        }
    }
}

生成された既存のコードを書きかえ

さらに既存のファイルを書きかえる:

app/build.gradle.kt

plugins {
    alias(libs.plugins.kotlin.jvm)
    application
    alias(libs.plugins.kotlin.plugin.serialization)
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(libs.ktor.server.core)
    implementation(libs.ktor.server.netty)
    implementation(libs.ktor.server.content.negotiation)
    implementation(libs.ktor.serialization.kotlinx.json)
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    mainClass.set("io.ktor.server.netty.EngineMain")
}

gradle/lib.versions.toml

[versions]
ktor-version = "3.0.3"
kotlin-version = "2.1.0"

[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor-version" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor-version" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor-version" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor-version" }
ktor-serialization-kotlinx-xml = { module = "io.ktor:ktor-serialization-kotlinx-xml", version.ref = "ktor-version" }
ktor-serialization-kotlinx-cbor = { module = "io.ktor:ktor-serialization-kotlinx-cbor", version.ref = "ktor-version" }
ktor-serialization-kotlinx-protobuf = { module = "io.ktor:ktor-serialization-kotlinx-protobuf", version.ref = "ktor-version" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }
kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin-version" }

この段階でのディレクトリ構成を確認:

$ tree .
.
├── app
│   ├── build.gradle.kts
│   └── src
│       └── main
│           ├── kotlin
│           │   └── Application.kt
│           └── resources
│               └── application.conf
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── settings.gradle.kts

かなりすっきりしました。

これで起動して作動を確かめます。

$ ./gradlew run

起動したら curl で作動を確かめます。

$ curl -X GET http://localhost:8080/
[
    {
        "id": 1,
        "name": "Squirtle"
    },
    {
        "id": 2,
        "name": "Charamander"
    },
    {
        "id": 3,
        "name": "Metapod"
    }
]

fatJar をつくる

java -jar fat.jar で起動できるようにします。

gradle/lib.versions.toml の plugins セクションに ktor の行を追加:

[plugins]
ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" }
...

さらに app/build.gradle.kts に fat jar をつくるための設定を追加:

plugins {
    alias(libs.plugins.ktor)
    ...
}

...

ktor {
    fatJar {
        archiveFileName.set("fat.jar")
    }
}

tasks に buildFatJar や runFatJar が追加されたことを確認:

$ ./gradlew tasks | grep Fat
buildFatJar - Builds a combined JAR of project and runtime dependencies.
runFatJar - Builds a combined JAR of project and runtime dependencies and runs it.

では fat jar を生成:

$ ./gradlew buildFatJar
$ find . -name fat.jar
./app/build/install/app-shadow/lib/fat.jar
./app/build/libs/fat.jar

実行します:

$ java -jar app/build/libs/fat.jar

id が 1 のポケモン情報を取得:

$ curl -X GET http://localhost:8080/1
{
    "id": 1,
    "name": "Squirtle"
}

できました。

以上です。