diff --git a/chat/build.gradle b/chat/build.gradle index c6cdae52..b9e4921d 100644 --- a/chat/build.gradle +++ b/chat/build.gradle @@ -3,11 +3,13 @@ buildscript { mavenCentral() } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" + classpath "org.jetbrains.kotlin:kotlin-serialization:2.0.20" } } apply plugin: 'kotlin-multiplatform' +apply plugin: 'org.jetbrains.kotlin.plugin.serialization' kotlin { targets { @@ -28,14 +30,14 @@ kotlin { sourceSets.each { it.dependencies { - implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:2.3.12")) + implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:3.0.0")) } } sourceSets { backendMain { dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" implementation "io.ktor:ktor-server-netty" implementation "io.ktor:ktor-server-websockets" implementation "io.ktor:ktor-server-call-logging" @@ -70,6 +72,7 @@ repositories { tasks.named("backendProcessResources").configure { dependsOn("frontendBrowserProductionWebpack") + dependsOn("frontendBrowserDistribution") } tasks.register("run", JavaExec) { diff --git a/chat/src/backendMain/kotlin/ChatApplication.kt b/chat/src/backendMain/kotlin/ChatApplication.kt index bb35c301..a9fe098e 100644 --- a/chat/src/backendMain/kotlin/ChatApplication.kt +++ b/chat/src/backendMain/kotlin/ChatApplication.kt @@ -4,7 +4,7 @@ import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.http.content.* import io.ktor.server.netty.* -import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.routing.* import io.ktor.server.sessions.* @@ -12,7 +12,9 @@ import io.ktor.server.websocket.* import io.ktor.util.* import io.ktor.websocket.* import kotlinx.coroutines.channels.* -import java.time.* +import kotlinx.serialization.Serializable +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes /** * An entry point of the application. @@ -61,7 +63,7 @@ class ChatApplication { // This installs the WebSockets plugin to be able to establish a bidirectional configuration // between the server and the client install(WebSockets) { - pingPeriod = Duration.ofMinutes(1) + pingPeriod = 1.minutes } // This enables the use of sessions to keep information between requests/refreshes of the browser. install(Sessions) { @@ -121,19 +123,14 @@ class ChatApplication { } // This defines a block of static resources for the '/' path (since no path is specified and we start at '/') - static { - // This marks index.html from the 'web' folder in resources as the default file to serve. - defaultResource("index.html", "web") - // This serves files from the 'web' folder in the application resources. - resources("web") - } - + staticResources("", "web") } } /** * A chat session is identified by a unique nonce ID. This nonce comes from a secure random source. */ + @Serializable data class ChatSession(val id: String) /** diff --git a/chat/src/backendTest/kotlin/ChatApplicationTest.kt b/chat/src/backendTest/kotlin/ChatApplicationTest.kt index b36c658d..1349e995 100644 --- a/chat/src/backendTest/kotlin/ChatApplicationTest.kt +++ b/chat/src/backendTest/kotlin/ChatApplicationTest.kt @@ -1,7 +1,7 @@ package io.ktor.samples.chat.backend import io.ktor.client.plugins.websocket.* -import io.ktor.server.application.* +import io.ktor.server.config.ApplicationConfig import io.ktor.server.testing.* import io.ktor.websocket.* import kotlin.test.* @@ -18,6 +18,9 @@ class ChatApplicationTest { // First, we create a [TestApplicationEngine] that includes the module [Application.main], // this executes that function and thus installs all the plugins and routes to this test application. testApplication { + environment { + config = ApplicationConfig(null) + } // Keeps a log array that will hold all the events we want to check later at once. val log = arrayListOf() @@ -57,6 +60,9 @@ class ChatApplicationTest { fun testDualConversation() { // Creates the [TestApplicationEngine] with the [Application::main] module. Check the previous test for more details. testApplication { + environment { + config = ApplicationConfig(null) + } // Sets to hold the messages from each children. // Since this is multithreaded and socket-related. // The order might change in each run, so we use a Set instead of a List to check that the messages diff --git a/chat/src/frontendMain/kotlin/main.kt b/chat/src/frontendMain/kotlin/main.kt index e333c2e8..16796685 100644 --- a/chat/src/frontendMain/kotlin/main.kt +++ b/chat/src/frontendMain/kotlin/main.kt @@ -30,6 +30,7 @@ fun main() { }) } +@OptIn(DelicateCoroutinesApi::class) suspend fun initConnection(wsClient: WsClient) { try { wsClient.connect() diff --git a/client-mpp/build.gradle b/client-mpp/build.gradle index 9d94b77a..1e99a9d3 100644 --- a/client-mpp/build.gradle +++ b/client-mpp/build.gradle @@ -5,7 +5,7 @@ buildscript { gradlePluginPortal() } dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20") classpath("com.android.tools.build:gradle:7.0.4") } } diff --git a/client-mpp/shared/build.gradle b/client-mpp/shared/build.gradle index ab5d665b..c7d7c094 100644 --- a/client-mpp/shared/build.gradle +++ b/client-mpp/shared/build.gradle @@ -19,7 +19,7 @@ kotlin { sourceSets.each { it.dependencies { - implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:2.3.12")) + implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:3.0.0")) } } diff --git a/client-multipart/build.gradle b/client-multipart/build.gradle index 576a9022..b0c4e707 100644 --- a/client-multipart/build.gradle +++ b/client-multipart/build.gradle @@ -4,8 +4,8 @@ buildscript { gradlePluginPortal() } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21" - classpath "io.ktor.plugin:plugin:2.3.12" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" + classpath "io.ktor.plugin:plugin:3.0.0" } } @@ -27,7 +27,7 @@ repositories { } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20' implementation "io.ktor:ktor-server-html-builder" implementation 'ch.qos.logback:logback-classic:1.4.6' implementation 'io.ktor:ktor-server-netty-jvm' diff --git a/client-tools/build.gradle b/client-tools/build.gradle index 3d3eb952..d20834b1 100644 --- a/client-tools/build.gradle +++ b/client-tools/build.gradle @@ -4,8 +4,8 @@ buildscript { gradlePluginPortal() } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21" - classpath "io.ktor.plugin:plugin:2.3.12" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" + classpath "io.ktor.plugin:plugin:3.0.0" } } @@ -27,7 +27,7 @@ repositories { } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20' implementation "io.ktor:ktor-server-html-builder" implementation 'ch.qos.logback:logback-classic:1.4.6' implementation 'io.ktor:ktor-server-netty-jvm' diff --git a/di-kodein/build.gradle.kts b/di-kodein/build.gradle.kts index c0741294..8a60bf51 100644 --- a/di-kodein/build.gradle.kts +++ b/di-kodein/build.gradle.kts @@ -2,9 +2,9 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" - kotlin("plugin.serialization") version "1.9.21" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" + kotlin("plugin.serialization") version "2.0.20" } application { @@ -23,6 +23,6 @@ dependencies { implementation("io.ktor:ktor-server-html-builder") implementation("org.kodein.di:kodein-di-jvm:7.17.0") implementation("ch.qos.logback:logback-classic:$logback_version") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } \ No newline at end of file diff --git a/di-kodein/gradle.properties b/di-kodein/gradle.properties index 97dde662..e018114d 100644 --- a/di-kodein/gradle.properties +++ b/di-kodein/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/filelisting/build.gradle.kts b/filelisting/build.gradle.kts index 9167ee43..d9615e77 100644 --- a/filelisting/build.gradle.kts +++ b/filelisting/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { @@ -20,7 +20,7 @@ dependencies { implementation("io.ktor:ktor-server-default-headers") implementation("io.ktor:ktor-server-html-builder") implementation("io.ktor:ktor-server-call-logging") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") implementation("ch.qos.logback:logback-classic:$logback_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } \ No newline at end of file diff --git a/filelisting/gradle.properties b/filelisting/gradle.properties index 97dde662..e018114d 100644 --- a/filelisting/gradle.properties +++ b/filelisting/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/filelisting/src/main/kotlin/io/ktor/samples/filelisting/FileListingApplication.kt b/filelisting/src/main/kotlin/io/ktor/samples/filelisting/FileListingApplication.kt index e631c658..4ed255d5 100644 --- a/filelisting/src/main/kotlin/io/ktor/samples/filelisting/FileListingApplication.kt +++ b/filelisting/src/main/kotlin/io/ktor/samples/filelisting/FileListingApplication.kt @@ -7,7 +7,7 @@ import io.ktor.server.html.* import io.ktor.server.http.content.* import io.ktor.server.netty.* import io.ktor.server.plugins.* -import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.request.* import io.ktor.server.response.* @@ -35,7 +35,7 @@ fun main() { call.respondInfo() } route("/myfiles") { - files(root) + staticFiles("", root) listing(root) } } @@ -53,7 +53,8 @@ suspend fun ApplicationCall.respondInfo() { respondHtml { body { style { - +""" + unsafe { + """ table { font: 1em Arial; border: 1px solid black; @@ -71,6 +72,7 @@ suspend fun ApplicationCall.respondInfo() { padding: 0.5em 1em; } """.trimIndent() + } } h1 { +"Ktor info" @@ -109,12 +111,10 @@ suspend fun ApplicationCall.respondInfo() { row("request.ranges()", request.ranges()) } - for ( - (name, value) in listOf( - "request.local" to request.local, - "request.origin" to request.origin - ) - ) { + for ((name, value) in listOf( + "request.local" to request.local, + "request.origin" to request.origin + )) { h2 { +name } @@ -122,19 +122,17 @@ suspend fun ApplicationCall.respondInfo() { row("$name.version", value.version) row("$name.method", value.method) row("$name.scheme", value.scheme) - row("$name.host", value.host) - row("$name.port", value.port) + row("$name.host", value.localHost) + row("$name.port", value.localPort) row("$name.remoteHost", value.remoteHost) row("$name.uri", value.uri) } } - for ( - (name, parameters) in listOf( - "Query parameters" to request.queryParameters, - "Headers" to request.headers - ) - ) { + for ((name, parameters) in listOf( + "Query parameters" to request.queryParameters, + "Headers" to request.headers + )) { h2 { +name } diff --git a/fullstack-mpp/build.gradle b/fullstack-mpp/build.gradle index 6b0e2d22..06250d0a 100644 --- a/fullstack-mpp/build.gradle +++ b/fullstack-mpp/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" } } @@ -29,7 +29,7 @@ kotlin { sourceSets.each { it.dependencies { - implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:2.3.12")) + implementation(project.dependencies.enforcedPlatform("io.ktor:ktor-bom:3.0.0")) } } diff --git a/graalvm/build.gradle.kts b/graalvm/build.gradle.kts index 758f79aa..82e1e8dc 100644 --- a/graalvm/build.gradle.kts +++ b/graalvm/build.gradle.kts @@ -1,7 +1,7 @@ plugins { application - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" id("org.graalvm.buildtools.native") version "0.9.19" } diff --git a/h2/build.gradle.kts b/h2/build.gradle.kts index 152a5fd4..d28ad380 100644 --- a/h2/build.gradle.kts +++ b/h2/build.gradle.kts @@ -3,8 +3,8 @@ val logback_version: String by project plugins { application - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } group = "io.ktor.samples" @@ -29,6 +29,6 @@ dependencies { implementation("io.ktor:ktor-server-netty-jvm") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-html-builder") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } diff --git a/h2/gradle.properties b/h2/gradle.properties index 97dde662..e018114d 100644 --- a/h2/gradle.properties +++ b/h2/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/httpbin/build.gradle.kts b/httpbin/build.gradle.kts index a7953021..9ede8131 100644 --- a/httpbin/build.gradle.kts +++ b/httpbin/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { @@ -31,7 +31,7 @@ dependencies { implementation("io.ktor:ktor-server-html-builder") implementation("io.ktor:ktor-server-partial-content") implementation("ch.qos.logback:logback-classic:$logback_version") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } diff --git a/httpbin/gradle.properties b/httpbin/gradle.properties index fa344247..a725dae7 100644 --- a/httpbin/gradle.properties +++ b/httpbin/gradle.properties @@ -1,5 +1,5 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 diff --git a/httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinApplication.kt b/httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinApplication.kt index 7c9558d5..0de58461 100644 --- a/httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinApplication.kt +++ b/httpbin/src/main/kotlin/io/ktor/samples/httpbin/HttpBinApplication.kt @@ -2,7 +2,6 @@ package io.ktor.samples.httpbin import com.google.gson.* import com.google.gson.reflect.* -import io.ktor.content.TextContent import io.ktor.http.* import io.ktor.http.content.* import io.ktor.serialization.gson.* @@ -11,8 +10,8 @@ import io.ktor.server.auth.* import io.ktor.server.html.* import io.ktor.server.http.content.* import io.ktor.server.plugins.* -import io.ktor.server.plugins.autohead.* -import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.autohead.AutoHeadResponse +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.compression.* import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.contentnegotiation.* @@ -446,7 +445,13 @@ fun Route.handleRequestWithBodyFor(method: HttpMethod) { contentType(ContentType.MultiPart.FormData) { method(method) { handle { - val listFiles = call.receive().readAllParts().filterIsInstance() + val listFiles = mutableListOf() + call.receive().forEachPart { + if (it is PartData.FileItem) { + listFiles.add(it) + } + } + call.sendHttpBinResponse { form = call.receive() files = listFiles.associateBy { part -> part.name ?: "a" } diff --git a/httpbin/src/test/kotlin/io/ktor/samples/httpbin/HttpBinApplicationTest.kt b/httpbin/src/test/kotlin/io/ktor/samples/httpbin/HttpBinApplicationTest.kt index 09c7bcfa..788d9994 100644 --- a/httpbin/src/test/kotlin/io/ktor/samples/httpbin/HttpBinApplicationTest.kt +++ b/httpbin/src/test/kotlin/io/ktor/samples/httpbin/HttpBinApplicationTest.kt @@ -1,6 +1,7 @@ package io.ktor.samples.httpbin import io.ktor.client.request.* +import io.ktor.server.config.ApplicationConfig import io.ktor.server.testing.* import org.junit.Test import kotlin.test.* @@ -15,6 +16,9 @@ class HttpBinApplicationTest { @Test fun testRedirect() { testApplication { + environment { + config = ApplicationConfig(null) + } val client = createClient { followRedirects = false } diff --git a/jwt-auth-tests/build.gradle.kts b/jwt-auth-tests/build.gradle.kts index 0e3b4869..7337de12 100644 --- a/jwt-auth-tests/build.gradle.kts +++ b/jwt-auth-tests/build.gradle.kts @@ -1,7 +1,7 @@ plugins { - kotlin("jvm") version "1.9.21" - kotlin("plugin.serialization") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + kotlin("plugin.serialization") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/ktor-client-wasm/gradle/libs.versions.toml b/ktor-client-wasm/gradle/libs.versions.toml index 02039f8c..a0607107 100644 --- a/ktor-client-wasm/gradle/libs.versions.toml +++ b/ktor-client-wasm/gradle/libs.versions.toml @@ -2,8 +2,8 @@ compose = "1.6.2" compose-plugin = "1.6.0" junit = "4.13.2" -kotlin = "1.9.22" -ktor = "3.0.0-beta-2-eap-920" +kotlin = "2.0.20" +ktor = "3.0.0" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } diff --git a/kweet/build.gradle.kts b/kweet/build.gradle.kts index 95818aa2..2b5c9ef1 100644 --- a/kweet/build.gradle.kts +++ b/kweet/build.gradle.kts @@ -3,9 +3,9 @@ val logback_version: String by project val exposed_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" - kotlin("plugin.serialization") version "1.9.21" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" + kotlin("plugin.serialization") version "2.0.20" } application { diff --git a/kweet/gradle.properties b/kweet/gradle.properties index 7cd9c292..02b2147a 100644 --- a/kweet/gradle.properties +++ b/kweet/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 exposed_version=0.40.1 kotlin.code.style=official diff --git a/kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt b/kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt index 076e7971..96f7f4b7 100644 --- a/kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt +++ b/kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt @@ -9,7 +9,7 @@ import io.ktor.samples.kweet.model.* import io.ktor.server.application.* import io.ktor.server.freemarker.* import io.ktor.server.plugins.* -import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.plugins.partialcontent.* @@ -65,6 +65,7 @@ class Logout() /** * Represents a session in this site containing the user ID. */ +@Serializable data class KweetSession(val userId: String) /** @@ -108,7 +109,7 @@ fun Application.main() { // First, we initialize the database. dao.init() // Then, we subscribe to the stop event of the application, so we can also close the [ComboPooledDataSource] [pool]. - environment.monitor.subscribe(ApplicationStopped) { pool.close() } + monitor.subscribe(ApplicationStopped) { pool.close() } // Now we call to a main with the dependencies as arguments. // Separating this function with its dependencies allows us to provide several modules with // the same code and different datasources living in the same application, diff --git a/kweet/src/test/kotlin/KweetApplicationTestLegacy.kt b/kweet/src/test/kotlin/KweetApplicationTestLegacy.kt deleted file mode 100644 index cc3fd555..00000000 --- a/kweet/src/test/kotlin/KweetApplicationTestLegacy.kt +++ /dev/null @@ -1,122 +0,0 @@ -import io.ktor.http.* -import io.ktor.samples.kweet.* -import io.ktor.samples.kweet.dao.* -import io.ktor.samples.kweet.model.* -import io.ktor.server.testing.* -import io.mockk.* -import org.joda.time.* -import org.junit.Test -import kotlin.test.* - -/** - * Integration tests for the module [mainWithDependencies]. - * - * Uses [testApp] in test methods to simplify the testing. - */ -class KweetApplicationTestLegacy { - /** - * A [mockk] instance of the [DAOFacade] to used to verify and mock calls on the integration tests. - */ - val dao = mockk(relaxed = true) - - /** - * Specifies a fixed date for testing. - */ - val date = DateTime.parse("2010-01-01T00:00+00:00") - - /** - * Tests that the [Index] page calls the [DAOFacade.top] and [DAOFacade.latest] methods just once. - * And that when no [Kweets] are available, it displays "There are no kweets yet" somewhere. - */ - @Test - fun testEmptyHome() = testApp { - every { dao.top() } returns listOf() - every { dao.latest() } returns listOf() - - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(200, response.status()?.value) - assertTrue(response.content!!.contains("There are no kweets yet")) - } - - verify(exactly = 1) { dao.top() } - verify(exactly = 1) { dao.latest() } - } - - /** - * Tests that the [Index] page calls the [DAOFacade.top] and [DAOFacade.latest] methods just once. - * And that when some Kweets are available there is a call to [DAOFacade.getKweet] per provided kweet id - * (the final application will cache with [DAOFacadeCache]). - * Ensures that it DOESN'T display "There are no kweets yet" when there are kweets available, - * and that the user of the kweets is also displayed. - */ - @Test - fun testHomeWithSomeKweets() = testApp { - every { dao.getKweet(1) } returns Kweet(1, "user1", "text1", date, null) - every { dao.getKweet(2) } returns Kweet(2, "user2", "text2", date, null) - every { dao.top() } returns listOf(1) - every { dao.latest() } returns listOf(2) - - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(200, response.status()?.value) - assertFalse(response.content!!.contains("There are no kweets yet")) - assertTrue(response.content!!.contains("user1")) - assertTrue(response.content!!.contains("user2")) - } - - verify(exactly = 2) { dao.getKweet(any()) } - verify(exactly = 1) { dao.top() } - verify(exactly = 1) { dao.latest() } - } - - /** - * Verifies the behaviour of a login failure. That it should be a redirection to the /user page. - */ - @Test - fun testLoginFail() = testApp { - handleRequest(HttpMethod.Post, "/login") { - addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) - setBody(listOf("userId" to "myuser", "password" to "invalid").formUrlEncode()) - }.apply { - assertEquals(302, response.status()?.value) - } - } - - /** - * Verifies a chain of requests verifying the [Login]. - * It mocks a get [DAOFacade.user] request, checks that posting valid credentials to the /login form - * redirects to the user [UserPage] for that user, and reuses the returned cookie for a request - * to the [UserPage] and verifies that with that cookie/session, there is a "sign out" text meaning that - * the user is logged in. - */ - @Test - fun testLoginSuccess() = testApp { - val password = "mylongpassword" - val passwordHash = hash(password) - val sessionCookieName = "SESSION" - lateinit var sessionCookie: Cookie - every { dao.user("test1", passwordHash) } returns User("test1", "test1@test.com", "test1", passwordHash) - - handleRequest(HttpMethod.Post, "/login") { - addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) - setBody(listOf("userId" to "test1", "password" to password).formUrlEncode()) - }.apply { - assertEquals(302, response.status()?.value) - assertEquals("/user/test1", response.headers["Location"]) - assertEquals(null, response.content) - sessionCookie = response.cookies[sessionCookieName]!! - } - - handleRequest(HttpMethod.Get, "/") { - addHeader(HttpHeaders.Cookie, "$sessionCookieName=${sessionCookie.value.encodeURLParameter()}") - }.apply { - assertTrue { response.content!!.contains("sign out") } - } - } - - /** - * A private method used to reduce boilerplate when testing the application. - */ - private fun testApp(callback: TestApplicationEngine.() -> Unit) { - withTestApplication({ mainWithDependencies(dao) }) { callback() } - } -} diff --git a/kweet/src/test/kotlin/KweetApplicationWithTrackCookiesTestLegacy.kt b/kweet/src/test/kotlin/KweetApplicationWithTrackCookiesTestLegacy.kt deleted file mode 100644 index d1f3d6e9..00000000 --- a/kweet/src/test/kotlin/KweetApplicationWithTrackCookiesTestLegacy.kt +++ /dev/null @@ -1,71 +0,0 @@ -import io.ktor.http.* -import io.ktor.samples.kweet.* -import io.ktor.samples.kweet.dao.* -import io.ktor.samples.kweet.model.* -import io.ktor.server.testing.* -import io.mockk.* -import org.joda.time.* -import org.junit.Test -import kotlin.test.* - -class KweetApplicationWithTrackCookiesTestLegacy { - val dao = mockk(relaxed = true) - val date = DateTime.parse("2010-01-01T00:00+00:00") - - /** - * This test is analogous to [KweetApplicationTestLegacy.testLoginSuccess] but uses the [cookiesSession] method - * to simplify the cookie tracking in several requests. - */ - @Test - fun testLoginSuccessWithTracker() = testApp { - val password = "mylongpassword" - val passwordHash = hash(password) - every { dao.user("test1", passwordHash) } returns User("test1", "test1@test.com", "test1", passwordHash) - - cookiesSession { - handleRequest(HttpMethod.Post, "/login") { - addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) - setBody(listOf("userId" to "test1", "password" to password).formUrlEncode()) - }.apply { - assertEquals(302, response.status()?.value) - assertEquals("/user/test1", response.headers["Location"]) - assertEquals(null, response.content) - } - - handleRequest(HttpMethod.Get, "/").apply { - assertTrue { response.content!!.contains("sign out") } - } - } - } - - private fun testApp(callback: TestApplicationEngine.() -> Unit) { - withTestApplication({ mainWithDependencies(dao) }) { callback() } - } -} - -private class CookieTrackerTestApplicationEngine( - val engine: TestApplicationEngine, - var trackedCookies: List = listOf() -) - -private fun CookieTrackerTestApplicationEngine.handleRequest( - method: HttpMethod, - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationCall { - return engine.handleRequest(method, uri) { - val cookieValue = - trackedCookies.joinToString("; ") { (it.name).encodeURLParameter() + "=" + (it.value).encodeURLParameter() } - addHeader(HttpHeaders.Cookie, cookieValue) - setup() - }.apply { - trackedCookies = response.headers.values(HttpHeaders.SetCookie).map { parseServerSetCookieHeader(it) } - } -} - -private fun TestApplicationEngine.cookiesSession( - initialCookies: List = listOf(), - callback: CookieTrackerTestApplicationEngine.() -> Unit -) { - callback(CookieTrackerTestApplicationEngine(this, initialCookies)) -} diff --git a/location-header/build.gradle.kts b/location-header/build.gradle.kts index a6d25111..ac836a23 100644 --- a/location-header/build.gradle.kts +++ b/location-header/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/location-header/gradle.properties b/location-header/gradle.properties index 97dde662..e018114d 100644 --- a/location-header/gradle.properties +++ b/location-header/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/maven-google-appengine-standard/pom.xml b/maven-google-appengine-standard/pom.xml index 1e8d00c2..0dc6e9a9 100644 --- a/maven-google-appengine-standard/pom.xml +++ b/maven-google-appengine-standard/pom.xml @@ -11,7 +11,7 @@ maven-google-appengine-standard - 1.9.21 + 2.0.20 1.7.36 2.0.12 4.0.1 @@ -37,7 +37,7 @@ io.ktor ktor-bom - 2.3.12 + 3.0.0 pom import diff --git a/mongodb/build.gradle.kts b/mongodb/build.gradle.kts index b1fb8d1d..36ddaf23 100644 --- a/mongodb/build.gradle.kts +++ b/mongodb/build.gradle.kts @@ -3,9 +3,9 @@ val logback_version: String by project val mongodb_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" - id("org.jetbrains.kotlin.plugin.serialization") version "1.9.21" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" + id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" } group = "com.example" @@ -30,9 +30,9 @@ dependencies { implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-config-yaml") implementation("org.litote.kmongo:kmongo:$mongodb_version") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") - testImplementation("io.ktor:ktor-server-test-host-jvm:2.3.12") + testImplementation("io.ktor:ktor-server-test-host-jvm:3.0.0") } tasks.register("databaseInstance") { diff --git a/mongodb/gradle.properties b/mongodb/gradle.properties index ed519ac5..92fcb6fa 100644 --- a/mongodb/gradle.properties +++ b/mongodb/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 mongodb_version=4.8.0 kotlin.code.style=official diff --git a/mvc-web/build.gradle.kts b/mvc-web/build.gradle.kts index 99e44436..8367077d 100644 --- a/mvc-web/build.gradle.kts +++ b/mvc-web/build.gradle.kts @@ -4,9 +4,9 @@ val h2_version: String by project val exposed_version: String by project plugins { - kotlin("jvm") version "1.9.20" + kotlin("jvm") version "2.0.20" id("io.ktor.plugin") version "2.3.6" - id("org.jetbrains.kotlin.plugin.serialization") version "1.9.20" + id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" } group = "com.example" @@ -35,6 +35,6 @@ dependencies { implementation("io.ktor:ktor-server-freemarker") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-status-pages") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } diff --git a/mvc-web/gradle.properties b/mvc-web/gradle.properties index 395040ca..78d7aac2 100644 --- a/mvc-web/gradle.properties +++ b/mvc-web/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.9.20 +kotlin_version=2.0.20 logback_version=1.4.11 exposed_version=0.41.1 h2_version=2.1.214 diff --git a/opentelemetry/client/build.gradle.kts b/opentelemetry/client/build.gradle.kts index f5cd6484..7b2018b3 100644 --- a/opentelemetry/client/build.gradle.kts +++ b/opentelemetry/client/build.gradle.kts @@ -4,8 +4,8 @@ val kotlin_version: String by project val opentelemetry_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" id("application") } diff --git a/opentelemetry/gradle.properties b/opentelemetry/gradle.properties index b0adf025..52d04cc9 100644 --- a/opentelemetry/gradle.properties +++ b/opentelemetry/gradle.properties @@ -1,5 +1,5 @@ -ktor_version=2.3.12 -kotlin_version=1.9.21 +ktor_version=3.0.0 +kotlin_version=2.0.20 logback_version=1.4.11 kotlin.code.style=official diff --git a/opentelemetry/server/build.gradle.kts b/opentelemetry/server/build.gradle.kts index 37796dcf..e8933283 100644 --- a/opentelemetry/server/build.gradle.kts +++ b/opentelemetry/server/build.gradle.kts @@ -4,8 +4,8 @@ val opentelemetry_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" id("application") } diff --git a/opentelemetry/shared/build.gradle.kts b/opentelemetry/shared/build.gradle.kts index 3bdcfec4..ac70b36a 100644 --- a/opentelemetry/shared/build.gradle.kts +++ b/opentelemetry/shared/build.gradle.kts @@ -2,8 +2,8 @@ val opentelemetry_version: String by project val opentelemetry_semconv_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } dependencies { diff --git a/postgres/build.gradle.kts b/postgres/build.gradle.kts index b98d7285..efc9d31d 100644 --- a/postgres/build.gradle.kts +++ b/postgres/build.gradle.kts @@ -2,9 +2,9 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" + kotlin("jvm") version "2.0.20" id("io.ktor.plugin") version "2.2.3" - id("org.jetbrains.kotlin.plugin.serialization") version "1.9.21" + id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" } group = "com.example" @@ -28,7 +28,7 @@ dependencies { implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-config-yaml") implementation("org.postgresql:postgresql:42.5.1") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } diff --git a/postgres/gradle.properties b/postgres/gradle.properties index 97dde662..e018114d 100644 --- a/postgres/gradle.properties +++ b/postgres/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/postgres/src/test/kotlin/com/example/ApplicationTest.kt b/postgres/src/test/kotlin/com/example/ApplicationTest.kt deleted file mode 100644 index b4b5f3d3..00000000 --- a/postgres/src/test/kotlin/com/example/ApplicationTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.example - -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.server.testing.* -import kotlin.test.* -import io.ktor.http.* -import com.example.plugins.* - -class ApplicationTest { - @Test - fun testRoot() = testApplication { - application { - configureRouting() - } - client.get("/").apply { - assertEquals(HttpStatusCode.OK, status) - assertEquals("Hello World!", bodyAsText()) - } - } -} diff --git a/redirect-with-exception/build.gradle.kts b/redirect-with-exception/build.gradle.kts index a148c6f7..38c7a1fc 100644 --- a/redirect-with-exception/build.gradle.kts +++ b/redirect-with-exception/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/redirect-with-exception/gradle.properties b/redirect-with-exception/gradle.properties index 97dde662..e018114d 100644 --- a/redirect-with-exception/gradle.properties +++ b/redirect-with-exception/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/reverse-proxy-ws/build.gradle.kts b/reverse-proxy-ws/build.gradle.kts index 7ffc43c4..27a352f3 100644 --- a/reverse-proxy-ws/build.gradle.kts +++ b/reverse-proxy-ws/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/reverse-proxy-ws/gradle.properties b/reverse-proxy-ws/gradle.properties index 97dde662..e018114d 100644 --- a/reverse-proxy-ws/gradle.properties +++ b/reverse-proxy-ws/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/reverse-proxy-ws/src/main/kotlin/io/ktor/samples/reverseproxyws/ReverseProxyWsApplication.kt b/reverse-proxy-ws/src/main/kotlin/io/ktor/samples/reverseproxyws/ReverseProxyWsApplication.kt index b9b17744..59ec8aa7 100644 --- a/reverse-proxy-ws/src/main/kotlin/io/ktor/samples/reverseproxyws/ReverseProxyWsApplication.kt +++ b/reverse-proxy-ws/src/main/kotlin/io/ktor/samples/reverseproxyws/ReverseProxyWsApplication.kt @@ -37,7 +37,7 @@ fun Application.module() { repliesDiv.appendChild(div); } - const ws = new WebSocket("ws://127.0.0.1:${call.request.origin.port}") + const ws = new WebSocket("ws://127.0.0.1:${call.request.origin.localPort}") ws.onopen = (e) => { addReply("Connected"); }; ws.onclose = (e) => { addReply("Disconnected"); }; ws.onerror = (e) => { addReply("Error " + e); }; diff --git a/reverse-proxy/build.gradle.kts b/reverse-proxy/build.gradle.kts index f0011ffe..8cd9e59a 100644 --- a/reverse-proxy/build.gradle.kts +++ b/reverse-proxy/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/reverse-proxy/gradle.properties b/reverse-proxy/gradle.properties index 97dde662..e018114d 100644 --- a/reverse-proxy/gradle.properties +++ b/reverse-proxy/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/rx/build.gradle.kts b/rx/build.gradle.kts index 8c4e1e13..56022f00 100644 --- a/rx/build.gradle.kts +++ b/rx/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { @@ -19,5 +19,5 @@ dependencies { implementation("io.ktor:ktor-server-netty-jvm") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.6.4") implementation("ch.qos.logback:logback-classic:$logback_version") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") } \ No newline at end of file diff --git a/rx/gradle.properties b/rx/gradle.properties index 97dde662..e018114d 100644 --- a/rx/gradle.properties +++ b/rx/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/sse/build.gradle.kts b/sse/build.gradle.kts index ff03a09e..3393af3d 100644 --- a/sse/build.gradle.kts +++ b/sse/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { diff --git a/sse/gradle.properties b/sse/gradle.properties index 97dde662..e018114d 100644 --- a/sse/gradle.properties +++ b/sse/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/structured-logging/build.gradle.kts b/structured-logging/build.gradle.kts index 3b4a1dfb..f84f9f4e 100644 --- a/structured-logging/build.gradle.kts +++ b/structured-logging/build.gradle.kts @@ -2,8 +2,8 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" } application { @@ -23,5 +23,5 @@ dependencies { implementation("org.jetbrains.kotlin-wrappers:kotlin-css:1.0.0-pre.519") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("net.logstash.logback:logstash-logback-encoder:7.1.1") - testImplementation("io.ktor:ktor-server-tests-jvm") + testImplementation("io.ktor:ktor-server-test-host-jvm") } \ No newline at end of file diff --git a/structured-logging/gradle.properties b/structured-logging/gradle.properties index 97dde662..e018114d 100644 --- a/structured-logging/gradle.properties +++ b/structured-logging/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/Application.kt b/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/Application.kt index dfc2aee1..83c4adf6 100644 --- a/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/Application.kt +++ b/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/Application.kt @@ -30,17 +30,17 @@ fun main() { fun Application.module() { intercept(ApplicationCallPipeline.Plugins) { val requestId = UUID.randomUUID() - logger.attach("req.Id", requestId.toString()) { - logger.info("Interceptor[start]") + call.logger.attach("req.Id", requestId.toString()) { + call.logger.info("Interceptor[start]") proceed() - logger.info("Interceptor[end]") + call.logger.info("Interceptor[end]") } } routing { get("/") { - logger.info("Respond[start]") + call.logger.info("Respond[start]") call.respondText("HELLO WORLD") - logger.info("Respond[end]") + call.logger.info("Respond[end]") } } } diff --git a/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/StructuredLogging.kt b/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/StructuredLogging.kt index 814e5823..d6f72a3f 100644 --- a/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/StructuredLogging.kt +++ b/structured-logging/src/main/kotlin/io/ktor/samples/structuredlogging/StructuredLogging.kt @@ -1,7 +1,7 @@ package io.ktor.samples.structuredlogging import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.application +import io.ktor.server.application.PipelineCall import io.ktor.server.application.call import io.ktor.server.application.log import io.ktor.util.pipeline.PipelineContext @@ -17,11 +17,9 @@ private val StructuredLoggerAttr = AttributeKey("StructuredLog * Obtains a logger for this [ApplicationCall] that allows to temporarily [StructuredLogger.attach] objects * to be associated to each log. */ -val PipelineContext.logger - get() = this.call.attributes.computeIfAbsent(StructuredLoggerAttr) { - StructuredLogger( - this.application.log - ) +val ApplicationCall.logger + get() = attributes.computeIfAbsent(StructuredLoggerAttr) { + StructuredLogger(application.log) } /** diff --git a/version-diff/build.gradle.kts b/version-diff/build.gradle.kts index 02958f59..d1f6ce0f 100644 --- a/version-diff/build.gradle.kts +++ b/version-diff/build.gradle.kts @@ -2,9 +2,9 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" - kotlin("plugin.serialization") version "1.9.21" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" + kotlin("plugin.serialization") version "2.0.20" } group = "org.example" diff --git a/version-diff/gradle.properties b/version-diff/gradle.properties index 97dde662..e018114d 100644 --- a/version-diff/gradle.properties +++ b/version-diff/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/version-diff/src/main/kotlin/Main.kt b/version-diff/src/main/kotlin/Main.kt index 67c4d406..328499ff 100644 --- a/version-diff/src/main/kotlin/Main.kt +++ b/version-diff/src/main/kotlin/Main.kt @@ -24,9 +24,9 @@ fun main(args: Array) = runBlocking { val client = HttpClient { install(HttpTimeout) { - requestTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS - connectTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS - socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS + requestTimeoutMillis = Long.MAX_VALUE + connectTimeoutMillis = Long.MAX_VALUE + socketTimeoutMillis = Long.MAX_VALUE } install(HttpRequestRetry) { exponentialDelay() diff --git a/youkube/build.gradle.kts b/youkube/build.gradle.kts index d4abf098..172319fc 100644 --- a/youkube/build.gradle.kts +++ b/youkube/build.gradle.kts @@ -2,9 +2,9 @@ val kotlin_version: String by project val logback_version: String by project plugins { - kotlin("jvm") version "1.9.21" - id("io.ktor.plugin") version "2.3.12" - kotlin("plugin.serialization") version "1.9.21" + kotlin("jvm") version "2.0.20" + id("io.ktor.plugin") version "3.0.0" + kotlin("plugin.serialization") version "2.0.20" } application { diff --git a/youkube/gradle.properties b/youkube/gradle.properties index 97dde662..e018114d 100644 --- a/youkube/gradle.properties +++ b/youkube/gradle.properties @@ -1,3 +1,3 @@ -kotlin_version=1.9.21 -logback_version=1.2.11 +kotlin_version=2.0.20 +logback_version=1.3.14 kotlin.code.style=official diff --git a/youkube/src/main/kotlin/io/ktor/samples/youkube/Page.kt b/youkube/src/main/kotlin/io/ktor/samples/youkube/Page.kt index 6da0f855..353eb10e 100644 --- a/youkube/src/main/kotlin/io/ktor/samples/youkube/Page.kt +++ b/youkube/src/main/kotlin/io/ktor/samples/youkube/Page.kt @@ -3,13 +3,14 @@ package io.ktor.samples.youkube import io.ktor.http.* import io.ktor.http.content.* import io.ktor.server.application.* -import io.ktor.server.html.HtmlContent import io.ktor.server.plugins.* import io.ktor.server.resources.* import io.ktor.server.response.* import io.ktor.server.sessions.* import io.ktor.util.date.* +import io.ktor.utils.io.charsets.Charsets import kotlinx.html.* +import kotlinx.html.stream.appendHTML /** * Generates HTML for the structure of the page and allows to provide a [block] that will be placed @@ -21,50 +22,54 @@ suspend fun ApplicationCall.respondDefaultHtml( title: String = "YouKube", block: DIV.() -> Unit ) { - val content = HtmlContent(HttpStatusCode.OK) { - val session = sessions.get() - head { - title { +title } - styleLink("http://yui.yahooapis.com/pure/0.6.0/pure-min.css") - styleLink("http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css") - styleLink(request.origin.run { - "$scheme://$host:$port${application.href(MainCss())}" - }) - } - body { - div("pure-g") { - div("sidebar pure-u-1 pure-u-md-1-4") { - div("header") { - div("brand-title") { +title } - div("brand-tagline") { - if (session != null) { - +session.userId + val text = buildString { + append("\n") + appendHTML().html(block = { + val session = sessions.get() + head { + title { +title } + styleLink("http://yui.yahooapis.com/pure/0.6.0/pure-min.css") + styleLink("http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css") + styleLink(request.origin.run { + "$scheme://$localHost:$localPort${application.href(MainCss())}" + }) + } + body { + div("pure-g") { + div("sidebar pure-u-1 pure-u-md-1-4") { + div("header") { + div("brand-title") { +title } + div("brand-tagline") { + if (session != null) { + +session.userId + } } - } - nav("nav") { - ul("nav-list") { - li("nav-item") { - if (session == null) { - a(classes = "pure-button", href = application.href(Login())) { +"Login" } - } else { - a(classes = "pure-button", href = application.href(Upload())) { +"Upload" } + nav("nav") { + ul("nav-list") { + li("nav-item") { + if (session == null) { + a(classes = "pure-button", href = application.href(Login())) { +"Login" } + } else { + a(classes = "pure-button", href = application.href(Upload())) { +"Upload" } + } + } + li("nav-item") { + a(classes = "pure-button", href = application.href(Index())) { +"Watch" } } - } - li("nav-item") { - a(classes = "pure-button", href = application.href(Index())) { +"Watch" } } } } } - } - div("content pure-u-1 pure-u-md-3-4") { - block() + div("content pure-u-1 pure-u-md-3-4") { + block() + } } } - } + }) } + val content = TextContent(text, ContentType.Text.Html.withCharset(Charsets.UTF_8), HttpStatusCode.OK) content.versions = versions content.caching = CachingOptions( cacheControl = CacheControl.MaxAge( diff --git a/youkube/src/main/kotlin/io/ktor/samples/youkube/Upload.kt b/youkube/src/main/kotlin/io/ktor/samples/youkube/Upload.kt index 8a286431..d70ec5c9 100644 --- a/youkube/src/main/kotlin/io/ktor/samples/youkube/Upload.kt +++ b/youkube/src/main/kotlin/io/ktor/samples/youkube/Upload.kt @@ -10,6 +10,8 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* import io.ktor.server.sessions.get +import io.ktor.util.cio.writeChannel +import io.ktor.utils.io.copyAndClose import kotlinx.coroutines.* import kotlinx.html.* import java.io.* @@ -80,7 +82,7 @@ fun Route.upload(database: Database, uploadDir: File) { "upload-${System.currentTimeMillis()}-${session.userId.hashCode()}-${title.hashCode()}.$ext" ) - part.streamProvider().use { its -> file.outputStream().buffered().use { its.copyToSuspend(it) } } + part.provider().copyAndClose(file.writeChannel()) videoFile = file } diff --git a/youkube/src/main/kotlin/io/ktor/samples/youkube/YoukubeApplication.kt b/youkube/src/main/kotlin/io/ktor/samples/youkube/YoukubeApplication.kt index a290fc42..e061742f 100644 --- a/youkube/src/main/kotlin/io/ktor/samples/youkube/YoukubeApplication.kt +++ b/youkube/src/main/kotlin/io/ktor/samples/youkube/YoukubeApplication.kt @@ -4,7 +4,7 @@ import io.ktor.http.* import io.ktor.resources.* import io.ktor.server.application.* import io.ktor.server.auth.* -import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.plugins.compression.* import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.defaultheaders.* @@ -57,6 +57,7 @@ class Index() /** * A session of this site, that just contains the [userId]. */ +@Serializable data class YouKubeSession(val userId: String) /** diff --git a/youkube/src/test/kotlin/YoukubeApplicationTest.kt b/youkube/src/test/kotlin/YoukubeApplicationTest.kt index 2fb397ce..11bb01fc 100644 --- a/youkube/src/test/kotlin/YoukubeApplicationTest.kt +++ b/youkube/src/test/kotlin/YoukubeApplicationTest.kt @@ -5,7 +5,9 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.samples.youkube.* +import io.ktor.server.config.ApplicationConfig import io.ktor.server.testing.* +import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.streams.* import java.io.* import kotlin.test.* @@ -20,6 +22,10 @@ class YoukubeApplicationTest { */ @Test fun testRootWithoutVideos() = testApplication { + environment { + config = ApplicationConfig(null) + } + client.get("/").apply { assertTrue { bodyAsText().contains("You need to upload some videos to watch them") } } @@ -31,11 +37,16 @@ class YoukubeApplicationTest { */ @Test fun testUploadVideo() = testApplication { + environment { + config = ApplicationConfig(null) + } + val uploadDir = File(".youkube-video") val client = createClient { install(HttpCookies) } client.get("/").apply { + println(bodyAsText()) assertTrue { bodyAsText().contains("You need to upload some videos to watch them") } } client.post("/login") { @@ -62,7 +73,7 @@ class YoukubeApplicationTest { ) ), PartData.FileItem( - { byteArrayOf(1, 2, 3).inputStream().asInput() }, {}, + { ByteReadChannel(byteArrayOf(1, 2, 3)) }, {}, headersOf( HttpHeaders.ContentDisposition, ContentDisposition.File diff --git a/youkube/src/test/kotlin/YoukubeApplicationTestLegacy.kt b/youkube/src/test/kotlin/YoukubeApplicationTestLegacy.kt deleted file mode 100644 index b09a92b9..00000000 --- a/youkube/src/test/kotlin/YoukubeApplicationTestLegacy.kt +++ /dev/null @@ -1,142 +0,0 @@ -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.samples.youkube.* -import io.ktor.server.config.* -import io.ktor.server.testing.* -import io.ktor.utils.io.streams.* -import org.junit.Test -import java.nio.file.* -import kotlin.test.* - -/** - * Integration tests for the [main] module. - */ -class YoukubeApplicationTestLegacy { - /** - * Verifies that the [Index] page, returns content with "You need to upload some videos to watch them" - * for an empty test application. - */ - @Test - fun testRootWithoutVideos() = testApp { - handleRequest(HttpMethod.Get, "/").apply { - assertTrue { response.content!!.contains("You need to upload some videos to watch them") } - } - } - - /** - * Verifies the complete process of [Login] with the valid credentials (root:root) for this application, - * obtains the [Index] verifying that it now offers [Upload]ing files and that it links to the newly created [Video]. - * Then it tries to access the [VideoPage] and ensures that it has a [kotlinx.html.VIDEO] element with the video. - * - * All this wrapped by a [cookiesSession] to be able to reuse [HttpHeaders.Cookie]/[HttpHeaders.SetCookie] - * among requests. - */ - @Test - fun testUploadVideo() = testApp { - cookiesSession { - handleRequest(HttpMethod.Post, "/login") { - addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) - setBody(listOf(Login::userName.name to "root", Login::password.name to "root").formUrlEncode()) - }.apply { - assertEquals(302, response.status()?.value) - assertEquals(null, response.content) - } - handleRequest(HttpMethod.Get, "/").apply { - assertTrue { response.content!!.contains("Upload") } - } - handleRequest(HttpMethod.Post, "/upload") { - val boundary = "***bbb***" - - addHeader( - HttpHeaders.ContentType, - ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString() - ) - setBody( - boundary, - listOf( - PartData.FormItem( - "title123", { }, - headersOf( - HttpHeaders.ContentDisposition, - ContentDisposition.Inline - .withParameter(ContentDisposition.Parameters.Name, "title") - .toString() - ) - ), - PartData.FileItem( - { byteArrayOf(1, 2, 3).inputStream().asInput() }, {}, - headersOf( - HttpHeaders.ContentDisposition, - ContentDisposition.File - .withParameter(ContentDisposition.Parameters.Name, "file") - .withParameter(ContentDisposition.Parameters.FileName, "file.txt") - .toString() - ) - ) - ) - ) - }.apply { - assertEquals(302, response.status()?.value) - assertEquals("/video/page/1", response.headers["Location"]) - } - - handleRequest(HttpMethod.Get, "/").apply { - assertFalse { response.content!!.contains("You need to upload some videos to watch them") } - assertTrue { response.content!!.contains("title123") } - } - - handleRequest(HttpMethod.Get, "/video/page/1").apply { - assertTrue { response.content!!.contains("") } - } - } - } - - /** - * Convenience method we use to configure a test application and to execute a [callback] block testing it. - */ - private fun testApp(callback: TestApplicationEngine.() -> Unit) { - val tempPath = Files.createTempDirectory(null).toFile().apply { deleteOnExit() } - try { - withTestApplication({ - (environment.config as MapApplicationConfig).apply { - put("youkube.session.cookie.key", "03e156f6058a13813816065") - put("youkube.upload.dir", tempPath.absolutePath) - } - main() - }, callback) - } finally { - tempPath.deleteRecursively() - } - } -} - -private class CookieTrackerTestApplicationEngine( - val engine: TestApplicationEngine, - var trackedCookies: List = listOf() -) - -private fun CookieTrackerTestApplicationEngine.handleRequest( - method: HttpMethod, - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationCall { - return engine.handleRequest(method, uri) { - val cookieValue = - trackedCookies.map { (it.name).encodeURLQueryComponent() + "=" + (it.value).encodeURLQueryComponent() } - .joinToString("; ") - addHeader("Cookie", cookieValue) - setup() - }.apply { - val setCookie = response.headers.values("Set-Cookie") - if (setCookie.isNotEmpty()) { - trackedCookies = setCookie.map { parseServerSetCookieHeader(it) } - } - } -} - -private fun TestApplicationEngine.cookiesSession( - initialCookies: List = listOf(), - callback: CookieTrackerTestApplicationEngine.() -> Unit -) { - callback(CookieTrackerTestApplicationEngine(this, initialCookies)) -}