diff --git a/ktor-server/ktor-server-servlet-jakarta/build.gradle.kts b/ktor-server/ktor-server-servlet-jakarta/build.gradle.kts index b081988c980..560172a9591 100644 --- a/ktor-server/ktor-server-servlet-jakarta/build.gradle.kts +++ b/ktor-server/ktor-server-servlet-jakarta/build.gradle.kts @@ -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) } diff --git a/ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletApplicationEngine.kt b/ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletApplicationEngine.kt index 2142154a5ad..ebee8b838a2 100644 --- a/ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletApplicationEngine.kt +++ b/ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletApplicationEngine.kt @@ -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.* @@ -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 ?: "/" diff --git a/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/application.yaml b/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/application.yaml new file mode 100644 index 00000000000..c22e3f82def --- /dev/null +++ b/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/application.yaml @@ -0,0 +1,4 @@ +ktor: + deployment: + port: 1234 +property: a diff --git a/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/custom-config.yaml b/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/custom-config.yaml new file mode 100644 index 00000000000..b30c4f7d75d --- /dev/null +++ b/ktor-server/ktor-server-servlet-jakarta/jvm/test-resources/custom-config.yaml @@ -0,0 +1,4 @@ +ktor: + deployment: + port: 1234 +property: a-custom diff --git a/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/ConfigTest.kt b/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/ConfigTest.kt new file mode 100644 index 00000000000..78196e2b40e --- /dev/null +++ b/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/ConfigTest.kt @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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() + 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()) + } + } +} diff --git a/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/HoconTest.kt b/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/HoconTest.kt deleted file mode 100644 index 923cbda6709..00000000000 --- a/ktor-server/ktor-server-servlet-jakarta/jvm/test/io/ktor/tests/servlet/jakarta/HoconTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 HoconTest { - @Test - fun resolveParametersFromCustomConfig() { - val engine = ServletApplicationEngine() - val pipeline = EnginePipeline() - - pipeline.intercept(EnginePipeline.Call) { - val value = call.application.environment.config.property("var").getString() - assertEquals("test", value) - } - - val context = mockk { - 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 { - 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() - } - - private fun getResponse(): HttpServletResponse { - val error = slot() - 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 Collections.enumeration(emptyList()) - } - } -} diff --git a/ktor-server/ktor-server-servlet/build.gradle.kts b/ktor-server/ktor-server-servlet/build.gradle.kts index ffa805e8207..7ee9a4bd58d 100644 --- a/ktor-server/ktor-server-servlet/build.gradle.kts +++ b/ktor-server/ktor-server-servlet/build.gradle.kts @@ -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) } diff --git a/ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletApplicationEngine.kt b/ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletApplicationEngine.kt index e26b084a053..fc789f1362a 100644 --- a/ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletApplicationEngine.kt +++ b/ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletApplicationEngine.kt @@ -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.* @@ -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 ?: "/" @@ -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 diff --git a/ktor-server/ktor-server-servlet/jvm/test-resources/application.yaml b/ktor-server/ktor-server-servlet/jvm/test-resources/application.yaml new file mode 100644 index 00000000000..c22e3f82def --- /dev/null +++ b/ktor-server/ktor-server-servlet/jvm/test-resources/application.yaml @@ -0,0 +1,4 @@ +ktor: + deployment: + port: 1234 +property: a diff --git a/ktor-server/ktor-server-servlet/jvm/test-resources/custom-config.yaml b/ktor-server/ktor-server-servlet/jvm/test-resources/custom-config.yaml new file mode 100644 index 00000000000..b30c4f7d75d --- /dev/null +++ b/ktor-server/ktor-server-servlet/jvm/test-resources/custom-config.yaml @@ -0,0 +1,4 @@ +ktor: + deployment: + port: 1234 +property: a-custom diff --git a/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/ConfigTest.kt b/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/ConfigTest.kt new file mode 100644 index 00000000000..1eb71ccbd27 --- /dev/null +++ b/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/ConfigTest.kt @@ -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 + +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.servlet.* +import io.ktor.server.servlet.ServletApplicationEngine.Companion.ApplicationEngineEnvironmentAttributeKey +import io.ktor.server.servlet.ServletApplicationEngine.Companion.ApplicationEnginePipelineAttributeKey +import io.mockk.* +import java.util.* +import javax.servlet.* +import javax.servlet.http.* +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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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() + 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 Collections.enumeration(emptyList()) + } + } +} diff --git a/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/HoconTest.kt b/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/HoconTest.kt deleted file mode 100644 index 8503f336370..00000000000 --- a/ktor-server/ktor-server-servlet/jvm/test/io/ktor/tests/servlet/HoconTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 - -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.servlet.* -import io.ktor.server.servlet.ServletApplicationEngine.Companion.ApplicationEngineEnvironmentAttributeKey -import io.ktor.server.servlet.ServletApplicationEngine.Companion.ApplicationEnginePipelineAttributeKey -import io.mockk.* -import java.util.* -import javax.servlet.* -import javax.servlet.http.* -import kotlin.test.* - -class HoconTest { - @Test - fun resolveParametersFromCustomConfig() { - val engine = ServletApplicationEngine() - val pipeline = EnginePipeline() - - pipeline.intercept(EnginePipeline.Call) { - val value = call.application.environment.config.property("var").getString() - assertEquals("test", value) - } - - val context = mockk { - 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 { - 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() - } - - private fun getResponse(): HttpServletResponse { - val error = slot() - 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 Collections.enumeration(emptyList()) - } - } -}