feat(server): add Ktor server module with health endpoint
New Gradle module :server (Kotlin/JVM) with Ktor 3.1.2 framework, configured as an embedded Netty HTTP server. server/src/main/kotlin/de/krisenvorrat/server/: - Application.kt: entry point using EngineMain for HOCON config - plugins/Routing.kt: GET /health endpoint returning 200 OK - plugins/Serialization.kt: ContentNegotiation with kotlinx.json Configuration: - application.conf (HOCON): host 0.0.0.0, port 8080, module reference - logback.xml: SLF4J/Logback console logging Build config: - server/build.gradle.kts: Ktor plugin with Fat JAR (server.jar) - libs.versions.toml: Ktor 3.1.2, Logback 1.5.18 dependencies - settings.gradle.kts: include(:server) - :server depends on :shared for common DTO models Tests: 2 tests (health endpoint, 404 on unknown route) via Ktor testApplication. Closes #40
This commit is contained in:
parent
c0c4978ccf
commit
cb190e61e9
10 changed files with 157 additions and 0 deletions
|
|
@ -7,4 +7,5 @@ plugins {
|
||||||
alias(libs.plugins.kotlin.serialization) apply false
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
alias(libs.plugins.hilt.android) apply false
|
alias(libs.plugins.hilt.android) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
|
alias(libs.plugins.ktor) apply false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ navigationCompose = "2.8.5"
|
||||||
kotlinxSerialization = "1.7.3"
|
kotlinxSerialization = "1.7.3"
|
||||||
kotlinxCoroutines = "1.9.0"
|
kotlinxCoroutines = "1.9.0"
|
||||||
mockk = "1.13.13"
|
mockk = "1.13.13"
|
||||||
|
ktor = "3.1.2"
|
||||||
|
logback = "1.5.18"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
|
@ -45,6 +47,13 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-
|
||||||
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
|
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
|
||||||
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
|
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
|
||||||
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
||||||
|
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
|
||||||
|
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
|
||||||
|
ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" }
|
||||||
|
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||||
|
ktor-server-config-yaml = { group = "io.ktor", name = "ktor-server-config-yaml", version.ref = "ktor" }
|
||||||
|
ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
|
||||||
|
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
@ -54,3 +63,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
|
||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||||
|
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
|
||||||
|
|
|
||||||
40
server/build.gradle.kts
Normal file
40
server/build.gradle.kts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.jvm)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.ktor)
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("de.krisenvorrat.server.ApplicationKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
ktor {
|
||||||
|
fatJar {
|
||||||
|
archiveFileName.set("server.jar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":shared"))
|
||||||
|
|
||||||
|
implementation(libs.ktor.server.core)
|
||||||
|
implementation(libs.ktor.server.netty)
|
||||||
|
implementation(libs.ktor.server.content.negotiation)
|
||||||
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
|
testImplementation(libs.ktor.server.test.host)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
testImplementation(libs.kotlinx.serialization.json)
|
||||||
|
}
|
||||||
15
server/src/main/kotlin/de/krisenvorrat/server/Application.kt
Normal file
15
server/src/main/kotlin/de/krisenvorrat/server/Application.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.krisenvorrat.server
|
||||||
|
|
||||||
|
import de.krisenvorrat.server.plugins.configureRouting
|
||||||
|
import de.krisenvorrat.server.plugins.configureSerialization
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.netty.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
EngineMain.main(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Application.module() {
|
||||||
|
configureSerialization()
|
||||||
|
configureRouting()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package de.krisenvorrat.server.plugins
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
internal fun Application.configureRouting() {
|
||||||
|
routing {
|
||||||
|
get("/health") {
|
||||||
|
call.respondText("OK", ContentType.Text.Plain, HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package de.krisenvorrat.server.plugins
|
||||||
|
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
internal fun Application.configureSerialization() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
prettyPrint = true
|
||||||
|
isLenient = false
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
9
server/src/main/resources/application.conf
Normal file
9
server/src/main/resources/application.conf
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
ktor {
|
||||||
|
deployment {
|
||||||
|
port = 8080
|
||||||
|
host = "0.0.0.0"
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
modules = [ de.krisenvorrat.server.ApplicationKt.module ]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
server/src/main/resources/logback.xml
Normal file
13
server/src/main/resources/logback.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<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="INFO">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<logger name="io.ktor" level="INFO"/>
|
||||||
|
</configuration>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package de.krisenvorrat.server
|
||||||
|
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ApplicationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_healthEndpoint_returnsOk() = testApplication {
|
||||||
|
application {
|
||||||
|
module()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val response = client.get("/health")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatusCode.OK, response.status)
|
||||||
|
assertEquals("OK", response.bodyAsText())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_unknownRoute_returns404() = testApplication {
|
||||||
|
application {
|
||||||
|
module()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val response = client.get("/nonexistent")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatusCode.NotFound, response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,3 +22,4 @@ dependencyResolutionManagement {
|
||||||
rootProject.name = "krisenvorrat"
|
rootProject.name = "krisenvorrat"
|
||||||
include(":app")
|
include(":app")
|
||||||
include(":shared")
|
include(":shared")
|
||||||
|
include(":server")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue