Skip to content

Commit

Permalink
KTOR-865 Support CIO server for WasmJS and JS (#4590)
Browse files Browse the repository at this point in the history
* Make server-test-host depend on CIO in common
* Expose test-base from test-suites to have access to it in server-cio tests
* Explicitly use CIO in JS/WasmJs server tests
* Move WebSocketEngineSuite to common
  • Loading branch information
whyoleg authored Jan 10, 2025
1 parent 980ccf0 commit b42d338
Show file tree
Hide file tree
Showing 34 changed files with 40 additions and 59 deletions.
2 changes: 1 addition & 1 deletion ktor-server/ktor-server-cio/api/ktor-server-cio.klib.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Klib ABI Dump
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
Expand Down
5 changes: 2 additions & 3 deletions ktor-server/ktor-server-cio/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
description = ""

kotlin.sourceSets {
jvmAndPosixMain {
commonMain {
dependencies {
api(project(":ktor-server:ktor-server-core"))
api(project(":ktor-http:ktor-http-cio"))
api(project(":ktor-shared:ktor-websockets"))
api(project(":ktor-network"))
}
}
jvmAndPosixTest {
commonTest {
dependencies {
api(project(":ktor-server:ktor-server-core"))
api(project(":ktor-client:ktor-client-cio"))
api(project(":ktor-server:ktor-server-test-suites"))
api(project(":ktor-server:ktor-server-test-base"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public class CIOApplicationEngine(
return this
}

override fun start(wait: Boolean): ApplicationEngine = runBlocking { startSuspend(wait) }
override fun start(wait: Boolean): ApplicationEngine = runBlockingBridge { startSuspend(wait) }

override suspend fun stopSuspend(gracePeriodMillis: Long, timeoutMillis: Long) {
stopRequest.complete()
Expand All @@ -96,7 +96,7 @@ public class CIOApplicationEngine(
}
}

override fun stop(gracePeriodMillis: Long, timeoutMillis: Long): Unit = runBlocking {
override fun stop(gracePeriodMillis: Long, timeoutMillis: Long): Unit = runBlockingBridge {
stopSuspend(gracePeriodMillis, timeoutMillis)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import io.ktor.server.engine.internal.*
import io.ktor.util.logging.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.io.IOException
import kotlinx.io.*
import kotlin.time.Duration.Companion.seconds

private val LOGGER = KtorSimpleLogger("io.ktor.server.cio.HttpServer")

/**
* Start a http server with [settings] invoking [handler] for every request
*/
Expand All @@ -37,18 +39,14 @@ public fun CoroutineScope.httpServer(
val selector = SelectorManager(coroutineContext)
val timeout = settings.connectionIdleTimeoutSeconds.seconds

val logger = KtorSimpleLogger(
HttpServer::class.simpleName ?: HttpServer::class.qualifiedName ?: HttpServer::class.toString()
)

val acceptJob = launch(serverJob + CoroutineName("accept-${settings.port}")) {
aSocket(selector).tcp().bind(settings.host, settings.port) {
reuseAddress = settings.reuseAddress
}.use { server ->
socket.complete(server)

val exceptionHandler = coroutineContext[CoroutineExceptionHandler]
?: DefaultUncaughtExceptionHandler(logger)
?: DefaultUncaughtExceptionHandler(LOGGER)

val connectionScope = CoroutineScope(
coroutineContext +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ package io.ktor.server.cio.internal
import kotlinx.coroutines.*

internal expect val Dispatchers.IOBridge: CoroutineDispatcher

internal expect fun <T> runBlockingBridge(block: suspend CoroutineScope.() -> T): T
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.ktor.http.*
import io.ktor.http.cio.*
import io.ktor.server.cio.*
import io.ktor.server.cio.backend.*
import io.ktor.server.cio.internal.*
import io.ktor.util.date.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
Expand All @@ -29,7 +30,7 @@ private val notFound404_11 = RequestResponseBuilder().apply {
* This is just an example demonstrating how to create CIO low-level http server
*/
@OptIn(DelicateCoroutinesApi::class)
fun main() {
fun example() {
val settings = HttpServerSettings()

GlobalScope.launch {
Expand Down Expand Up @@ -64,7 +65,7 @@ fun main() {
}
)

runBlocking {
runBlockingBridge {
server.rootServerJob.join()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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.server.cio.internal

import kotlinx.coroutines.*

internal actual val Dispatchers.IOBridge: CoroutineDispatcher
get() = Default

internal actual fun <T> runBlockingBridge(block: suspend CoroutineScope.() -> T): T =
error("runBlocking is not supported on JS and WASM")
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ import kotlinx.coroutines.*

internal actual val Dispatchers.IOBridge: CoroutineDispatcher
get() = IO

internal actual fun <T> runBlockingBridge(block: suspend CoroutineScope.() -> T): T = runBlocking(block = block)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import io.ktor.network.sockets.*
import io.ktor.util.network.*

internal actual fun SocketAddress.toNetworkAddress(): NetworkAddress {
val inetAddress = this as? InetSocketAddress ?: error("Expected inet socket address")
return NetworkAddress(inetAddress.hostname, inetAddress.port)
check(this is InetSocketAddress) { "Expected inet socket address" }
return NetworkAddress(hostname, port)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ import kotlinx.coroutines.*

internal actual val Dispatchers.IOBridge: CoroutineDispatcher
get() = IO

internal actual fun <T> runBlockingBridge(block: suspend CoroutineScope.() -> T): T = runBlocking(block = block)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.ktor.server.test.base

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
Expand Down Expand Up @@ -132,7 +133,6 @@ actual constructor(
val starting = GlobalScope.async {
server.startSuspend(wait = false)
_port = server.engine.resolvedConnectors().first().port
delay(500)
}

return try {
Expand Down Expand Up @@ -161,7 +161,7 @@ actual constructor(
builder: suspend HttpRequestBuilder.() -> Unit,
block: suspend HttpResponse.(Int) -> Unit
) {
HttpClient {
HttpClient(CIO) {
followRedirects = false
expectSuccess = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ actual constructor(
// as far as we have retry loop on call side
val starting = GlobalScope.async {
server.start(wait = false)
delay(500)
// await for a server to be started
server.engine.resolvedConnectors()
}

return try {
Expand Down
7 changes: 1 addition & 6 deletions ktor-server/ktor-server-test-host/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,13 @@ val jetty_alpn_boot_version: String? by extra
kotlin.sourceSets {
commonMain {
dependencies {
api(project(":ktor-client:ktor-client-cio"))
api(project(":ktor-server:ktor-server-core"))
api(project(":ktor-client:ktor-client-core"))
api(project(":ktor-test-dispatcher"))
}
}

jvmAndPosixMain {
dependencies {
api(project(":ktor-client:ktor-client-cio"))
}
}

jvmMain {
dependencies {
api(project(":ktor-network:ktor-network-tls"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.ktor.client.request.*
import io.ktor.util.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.io.*
import io.ktor.network.sockets.SocketTimeoutException as NetworkSocketTimeoutException

/**
* [on] function receiver object
Expand Down Expand Up @@ -67,10 +67,3 @@ internal fun Throwable.mapToKtor(data: HttpRequestData): Throwable = when {
cause?.rootCause is NetworkSocketTimeoutException -> SocketTimeoutException(data, cause?.rootCause)
else -> this
}

// There are two SocketTimeoutException in ktor:
// * io.ktor.network.sockets.SocketTimeoutException
// * io.ktor.client.network.sockets.SocketTimeoutException
// on JVM they both are java.net.SocketTimeoutException, but on other targets it's not true
// additionally `network.sockets` exception is in `ktor-network` modules which don't have support for js/wasm target
internal expect class NetworkSocketTimeoutException(message: String) : IOException

This file was deleted.

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion ktor-server/ktor-server-test-suites/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kotlin.sourceSets {
implementation(project(":ktor-server:ktor-server-plugins:ktor-server-status-pages"))
implementation(project(":ktor-server:ktor-server-plugins:ktor-server-hsts"))
implementation(project(":ktor-server:ktor-server-plugins:ktor-server-websockets"))
implementation(project(":ktor-server:ktor-server-test-base"))
api(project(":ktor-server:ktor-server-test-base"))
}
}

Expand Down

0 comments on commit b42d338

Please sign in to comment.