Skip to content

Commit

Permalink
KTOR-6108 KtorServlet does not support yaml configuration (#3696)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsinukov authored Jul 19, 2023
1 parent 84f27d6 commit 6532273
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 183 deletions.
1 change: 1 addition & 0 deletions ktor-server/ktor-server-servlet-jakarta/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ kotlin.sourceSets {
jvmTest {
dependencies {
api(project(":ktor-server:ktor-server-core", configuration = "testOutput"))
api(project(":ktor-server:ktor-server-config-yaml"))
implementation(libs.mockk)
implementation(libs.jakarta.servlet)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

package io.ktor.server.servlet.jakarta

import com.typesafe.config.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.config.ConfigLoader.Companion.load
import io.ktor.server.engine.*
import io.ktor.util.*
import jakarta.servlet.*
import jakarta.servlet.annotation.*
import org.slf4j.*
import kotlin.coroutines.*
Expand All @@ -31,30 +30,22 @@ public open class ServletApplicationEngine : KtorServlet() {
servletContext.initParameterNames?.toList().orEmpty() +
servletConfig.initParameterNames?.toList().orEmpty()
).filter { it.startsWith("io.ktor") }.distinct()
val parameters = parameterNames.associateBy(
{ it.removePrefix("io.ktor.") },
{ servletConfig.getInitParameter(it) ?: servletContext.getInitParameter(it) }
)
val parameters = parameterNames.map {
it.removePrefix("io.ktor.") to
(servletConfig.getInitParameter(it) ?: servletContext.getInitParameter(it))
}

val hocon = ConfigFactory.parseMap(parameters)
val parametersConfig = MapApplicationConfig(parameters)
val configPath = "ktor.config"
val applicationIdPath = "ktor.application.id"

val combinedConfig = if (hocon.hasPath(configPath)) {
val configStream = servletContext.classLoader.getResourceAsStream(hocon.getString(configPath))
?: throw ServletException(
"No config ${hocon.getString(configPath)} found for the servlet named $servletName"
)
val loadedKtorConfig = configStream.bufferedReader().use { ConfigFactory.parseReader(it) }
hocon.withFallback(loadedKtorConfig).resolve()
} else {
hocon.withFallback(ConfigFactory.load())
}
val combinedConfig = parametersConfig
.withFallback(ConfigLoader.load(parametersConfig.tryGetString(configPath)))

val applicationId = combinedConfig.tryGetString(applicationIdPath) ?: "Application"

applicationEngineEnvironment {
config = HoconApplicationConfig(combinedConfig)
config = combinedConfig
log = LoggerFactory.getLogger(applicationId)
classLoader = servletContext.classLoader
rootPath = servletContext.contextPath ?: "/"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ktor:
deployment:
port: 1234
property: a
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ktor:
deployment:
port: 1234
property: a-custom
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.tests.servlet.jakarta

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.servlet.jakarta.*
import io.ktor.server.servlet.jakarta.ServletApplicationEngine.Companion.ApplicationEngineEnvironmentAttributeKey
import io.ktor.server.servlet.jakarta.ServletApplicationEngine.Companion.ApplicationEnginePipelineAttributeKey
import io.mockk.*
import jakarta.servlet.*
import jakarta.servlet.http.*
import java.util.*
import kotlin.test.*

class ConfigTest {
@Test
fun resolveParametersFromCustomConfig() {
val engine = ServletApplicationEngine()
val pipeline = EnginePipeline()

var interceptorCalled = false
pipeline.intercept(EnginePipeline.Call) {
val value = call.application.environment.config.property("var").getString()
assertEquals("test", value)
interceptorCalled = true
}

val context = mockk<ServletContext> {
every { getAttribute(ApplicationEnginePipelineAttributeKey) } returns pipeline
every { initParameterNames } returns Collections.enumeration(listOf("io.ktor.ktor.config"))
every { classLoader } returns this::class.java.classLoader
every { contextPath } returns "/"
every { getAttribute(ApplicationEngineEnvironmentAttributeKey) } returns null
}

val config = mockk<ServletConfig> {
every { getInitParameter("io.ktor.ktor.config") } returns "test.conf"
every { servletContext } returns context
every { servletName } returns "ktor-test"
every { initParameterNames } returns Collections.enumeration(emptyList())
}

engine.init(config)
engine.service(getRequest(), getResponse())
engine.destroy()
assertTrue(interceptorCalled)
}

@Test
fun resolveYamlFromCustomConfig() {
val engine = ServletApplicationEngine()
val pipeline = EnginePipeline()

var interceptorCalled = false
pipeline.intercept(EnginePipeline.Call) {
val value = call.application.environment.config.property("property").getString()
assertEquals("a-custom", value)
interceptorCalled = true
}

val context = mockk<ServletContext> {
every { getAttribute(ApplicationEnginePipelineAttributeKey) } returns pipeline
every { initParameterNames } returns Collections.enumeration(listOf("io.ktor.ktor.config"))
every { classLoader } returns this::class.java.classLoader
every { contextPath } returns "/"
every { getAttribute(ApplicationEngineEnvironmentAttributeKey) } returns null
}

val config = mockk<ServletConfig> {
every { getInitParameter("io.ktor.ktor.config") } returns "custom-config.yaml"
every { servletContext } returns context
every { servletName } returns "ktor-test"
every { initParameterNames } returns Collections.enumeration(emptyList())
}

engine.init(config)
engine.service(getRequest(), getResponse())
engine.destroy()
assertTrue(interceptorCalled)
}

@Test
fun resolveYamlFromDefaultConfig() {
val engine = ServletApplicationEngine()
val pipeline = EnginePipeline()

var interceptorCalled = false
pipeline.intercept(EnginePipeline.Call) {
val value = call.application.environment.config.property("property").getString()
assertEquals("a", value)
interceptorCalled = true
}

val context = mockk<ServletContext> {
every { getAttribute(ApplicationEnginePipelineAttributeKey) } returns pipeline
every { initParameterNames } returns Collections.enumeration(emptyList())
every { classLoader } returns this::class.java.classLoader
every { contextPath } returns "/"
every { getAttribute(ApplicationEngineEnvironmentAttributeKey) } returns null
}

val config = mockk<ServletConfig> {
every { getInitParameter("io.ktor.ktor.config") } returns "custom-config.yaml"
every { servletContext } returns context
every { servletName } returns "ktor-test"
every { initParameterNames } returns Collections.enumeration(emptyList())
}

engine.init(config)
engine.service(getRequest(), getResponse())
engine.destroy()
assertTrue(interceptorCalled)
}

private fun getResponse(): HttpServletResponse {
val error = slot<String>()
return mockk {
every { sendError(500, capture(error)) } answers {
fail(error.captured)
}
every { isCommitted } returns false
}
}

private fun getRequest(): HttpServletRequest {
return mockk {
every { isAsyncSupported } returns false
every { queryString } returns ""
every { requestURI } returns "/"
every { protocol } returns "HTTP/1.1"
every { scheme } returns "http"
every { method } returns "GET"
every { serverPort } returns 80
every { serverName } returns "server"
every { remoteHost } returns "localhost"
every { attributeNames } returns java.util.Collections.enumeration(emptyList())
}
}
}

This file was deleted.

1 change: 1 addition & 0 deletions ktor-server/ktor-server-servlet/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ kotlin.sourceSets {
jvmTest {
dependencies {
api(project(":ktor-server:ktor-server-core", configuration = "testOutput"))
api(project(":ktor-server:ktor-server-config-yaml"))
implementation(libs.mockk)
implementation(libs.javax.servlet)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

package io.ktor.server.servlet

import com.typesafe.config.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.config.ConfigLoader.Companion.load
import io.ktor.server.engine.*
import io.ktor.util.*
import org.slf4j.*
import javax.servlet.*
import javax.servlet.annotation.*
import kotlin.coroutines.*

Expand All @@ -31,30 +30,22 @@ public open class ServletApplicationEngine : KtorServlet() {
servletContext.initParameterNames?.toList().orEmpty() +
servletConfig.initParameterNames?.toList().orEmpty()
).filter { it.startsWith("io.ktor") }.distinct()
val parameters = parameterNames.associateBy(
{ it.removePrefix("io.ktor.") },
{ servletConfig.getInitParameter(it) ?: servletContext.getInitParameter(it) }
)
val parameters = parameterNames.map {
it.removePrefix("io.ktor.") to
(servletConfig.getInitParameter(it) ?: servletContext.getInitParameter(it))
}

val hocon = ConfigFactory.parseMap(parameters)
val parametersConfig = MapApplicationConfig(parameters)
val configPath = "ktor.config"
val applicationIdPath = "ktor.application.id"

val combinedConfig = if (hocon.hasPath(configPath)) {
val configStream = servletContext.classLoader.getResourceAsStream(hocon.getString(configPath))
?: throw ServletException(
"No config ${hocon.getString(configPath)} found for the servlet named $servletName"
)
val loadedKtorConfig = configStream.bufferedReader().use { ConfigFactory.parseReader(it) }
hocon.withFallback(loadedKtorConfig).resolve()
} else {
hocon.withFallback(ConfigFactory.load())
}
val combinedConfig = parametersConfig
.withFallback(ConfigLoader.load(parametersConfig.tryGetString(configPath)))

val applicationId = combinedConfig.tryGetString(applicationIdPath) ?: "Application"

applicationEngineEnvironment {
config = HoconApplicationConfig(combinedConfig)
config = combinedConfig
log = LoggerFactory.getLogger(applicationId)
classLoader = servletContext.classLoader
rootPath = servletContext.contextPath ?: "/"
Expand All @@ -81,7 +72,7 @@ public open class ServletApplicationEngine : KtorServlet() {
}

override val upgrade: ServletUpgrade by lazy {
if ("jetty" in servletContext.serverInfo?.toLowerCasePreservingASCIIRules() ?: "") {
if ("jetty" in (servletContext.serverInfo?.toLowerCasePreservingASCIIRules() ?: "")) {
jettyUpgrade ?: DefaultServletUpgrade
} else {
DefaultServletUpgrade
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ktor:
deployment:
port: 1234
property: a
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ktor:
deployment:
port: 1234
property: a-custom
Loading

0 comments on commit 6532273

Please sign in to comment.