Skip to content

Commit a99b014

Browse files
whyolegosipxd
authored andcommitted
KTOR-6004 Support TCP and Unix socket for wasm-js and js via NodeJS (#4411)
* Drop `jvmAndNix` shared source set * Commonize `ktor-network` and `ktor-network-tls` * Support TCP and Unix sockets for wasm-js and js on Node * Move `supportsUnixDomainSockets` to posix and use Platform instead of expect/actual
1 parent 31fedde commit a99b014

File tree

62 files changed

+781
-235
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+781
-235
lines changed

buildSrc/src/main/kotlin/TargetsConfig.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ private val hierarchyTemplate = KotlinHierarchyTemplate {
114114
group("windows")
115115
group("macos")
116116
}
117+
118+
group("nonJvm") {
119+
group("posix")
120+
group("jsAndWasmShared")
121+
}
117122
}
118123
}
119124

ktor-network/api/ktor-network.klib.api

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Klib ABI Dump
2-
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
2+
// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
3+
// Alias: native => [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
34
// Rendering settings:
45
// - Signature version: 2
56
// - Show manifest properties: true
@@ -36,11 +37,6 @@ abstract interface <#A: out io.ktor.network.sockets/Configurable<#A, #B>, #B: io
3637
open fun configure(kotlin/Function1<#B, kotlin/Unit>): #A // io.ktor.network.sockets/Configurable.configure|configure(kotlin.Function1<1:1,kotlin.Unit>){}[0]
3738
}
3839

39-
abstract interface io.ktor.network.selector/Selectable { // io.ktor.network.selector/Selectable|null[0]
40-
abstract val descriptor // io.ktor.network.selector/Selectable.descriptor|{}descriptor[0]
41-
abstract fun <get-descriptor>(): kotlin/Int // io.ktor.network.selector/Selectable.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
42-
}
43-
4440
abstract interface io.ktor.network.selector/SelectorManager : io.ktor.utils.io.core/Closeable, kotlinx.coroutines/CoroutineScope { // io.ktor.network.selector/SelectorManager|null[0]
4541
abstract fun notifyClosed(io.ktor.network.selector/Selectable) // io.ktor.network.selector/SelectorManager.notifyClosed|notifyClosed(io.ktor.network.selector.Selectable){}[0]
4642
abstract suspend fun select(io.ktor.network.selector/Selectable, io.ktor.network.selector/SelectInterest) // io.ktor.network.selector/SelectorManager.select|select(io.ktor.network.selector.Selectable;io.ktor.network.selector.SelectInterest){}[0]
@@ -103,10 +99,6 @@ final class io.ktor.network.selector/ClosedChannelCancellationException : kotlin
10399
constructor <init>() // io.ktor.network.selector/ClosedChannelCancellationException.<init>|<init>(){}[0]
104100
}
105101

106-
final class io.ktor.network.selector/SocketError : kotlin/IllegalStateException { // io.ktor.network.selector/SocketError|null[0]
107-
constructor <init>() // io.ktor.network.selector/SocketError.<init>|<init>(){}[0]
108-
}
109-
110102
final class io.ktor.network.sockets/Connection { // io.ktor.network.sockets/Connection|null[0]
111103
constructor <init>(io.ktor.network.sockets/Socket, io.ktor.utils.io/ByteReadChannel, io.ktor.utils.io/ByteWriteChannel) // io.ktor.network.sockets/Connection.<init>|<init>(io.ktor.network.sockets.Socket;io.ktor.utils.io.ByteReadChannel;io.ktor.utils.io.ByteWriteChannel){}[0]
112104

@@ -284,3 +276,17 @@ final fun <#A: io.ktor.network.sockets/Configurable<#A, *>> (#A).io.ktor.network
284276
final fun io.ktor.network.selector/SelectorManager(kotlin.coroutines/CoroutineContext = ...): io.ktor.network.selector/SelectorManager // io.ktor.network.selector/SelectorManager|SelectorManager(kotlin.coroutines.CoroutineContext){}[0]
285277
final fun io.ktor.network.sockets/aSocket(io.ktor.network.selector/SelectorManager): io.ktor.network.sockets/SocketBuilder // io.ktor.network.sockets/aSocket|aSocket(io.ktor.network.selector.SelectorManager){}[0]
286278
final suspend fun (io.ktor.network.sockets/ASocket).io.ktor.network.sockets/awaitClosed() // io.ktor.network.sockets/awaitClosed|[email protected](){}[0]
279+
280+
// Targets: [native]
281+
abstract interface io.ktor.network.selector/Selectable { // io.ktor.network.selector/Selectable|null[0]
282+
abstract val descriptor // io.ktor.network.selector/Selectable.descriptor|{}descriptor[0]
283+
abstract fun <get-descriptor>(): kotlin/Int // io.ktor.network.selector/Selectable.descriptor.<get-descriptor>|<get-descriptor>(){}[0]
284+
}
285+
286+
// Targets: [native]
287+
final class io.ktor.network.selector/SocketError : kotlin/IllegalStateException { // io.ktor.network.selector/SocketError|null[0]
288+
constructor <init>() // io.ktor.network.selector/SocketError.<init>|<init>(){}[0]
289+
}
290+
291+
// Targets: [js, wasmJs]
292+
abstract interface io.ktor.network.selector/Selectable // io.ktor.network.selector/Selectable|null[0]

ktor-network/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ kotlin {
1010
}
1111

1212
sourceSets {
13-
jvmAndPosixMain {
13+
commonMain {
1414
dependencies {
1515
api(project(":ktor-utils"))
1616
}
1717
}
1818

19-
jvmAndPosixTest {
19+
commonTest {
2020
dependencies {
2121
api(project(":ktor-test-dispatcher"))
2222
}

ktor-network/jvmAndPosix/src/io/ktor/network/selector/SelectorManagerCommon.kt renamed to ktor-network/common/src/io/ktor/network/selector/SelectorManagerCommon.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import kotlin.coroutines.*
1111
/**
1212
* Creates the selector manager for current platform.
1313
*/
14-
@Suppress("FunctionName")
1514
public expect fun SelectorManager(
1615
dispatcher: CoroutineContext = EmptyCoroutineContext
1716
): SelectorManager

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Datagram.kt renamed to ktor-network/common/src/io/ktor/network/sockets/Datagram.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3-
*/
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
44

55
package io.ktor.network.sockets
66

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketAddress.kt renamed to ktor-network/common/src/io/ktor/network/sockets/SocketAddress.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package io.ktor.network.sockets
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.network.sockets
6+
7+
import io.ktor.network.selector.*
8+
9+
internal expect suspend fun tcpConnect(
10+
selector: SelectorManager,
11+
remoteAddress: SocketAddress,
12+
socketOptions: SocketOptions.TCPClientSocketOptions
13+
): Socket
14+
15+
internal expect suspend fun tcpBind(
16+
selector: SelectorManager,
17+
localAddress: SocketAddress?,
18+
socketOptions: SocketOptions.AcceptorOptions
19+
): ServerSocket
20+
21+
internal expect suspend fun udpConnect(
22+
selector: SelectorManager,
23+
remoteAddress: SocketAddress,
24+
localAddress: SocketAddress?,
25+
options: SocketOptions.UDPSocketOptions
26+
): ConnectedDatagramSocket
27+
28+
internal expect suspend fun udpBind(
29+
selector: SelectorManager,
30+
localAddress: SocketAddress?,
31+
options: SocketOptions.UDPSocketOptions
32+
): BoundDatagramSocket

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/SocketOptions.kt renamed to ktor-network/common/src/io/ktor/network/sockets/SocketOptions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
package io.ktor.network.sockets
66

7-
internal const val INFINITE_TIMEOUT_MS = Long.MAX_VALUE
7+
private const val INFINITE_TIMEOUT_MS = Long.MAX_VALUE
88

99
/**
1010
* Socket options builder
@@ -29,7 +29,7 @@ public sealed class SocketOptions(
2929
}
3030
}
3131

32-
internal fun acceptor(): AcceptorOptions {
32+
internal fun tcpAccept(): AcceptorOptions {
3333
return AcceptorOptions(HashMap(customOptions)).apply {
3434
copyCommon(this@SocketOptions)
3535
}
@@ -112,7 +112,7 @@ public sealed class SocketOptions(
112112
}
113113
}
114114

115-
internal fun tcp(): TCPClientSocketOptions {
115+
internal fun tcpConnect(): TCPClientSocketOptions {
116116
return TCPClientSocketOptions(HashMap(customOptions)).apply {
117117
copyCommon(this@PeerSocketOptions)
118118
}

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/Sockets.kt renamed to ktor-network/common/src/io/ktor/network/sockets/Sockets.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3-
*/
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
44

55
package io.ktor.network.sockets
66

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TcpSocketBuilder.kt renamed to ktor-network/common/src/io/ktor/network/sockets/TcpSocketBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ public class TcpSocketBuilder internal constructor(
3737
public suspend fun connect(
3838
remoteAddress: SocketAddress,
3939
configure: SocketOptions.TCPClientSocketOptions.() -> Unit = {}
40-
): Socket = connect(selector, remoteAddress, options.tcp().apply(configure))
40+
): Socket = tcpConnect(selector, remoteAddress, options.tcpConnect().apply(configure))
4141

4242
/**
4343
* Bind server socket to listen to [localAddress].
4444
*/
4545
public suspend fun bind(
4646
localAddress: SocketAddress? = null,
4747
configure: SocketOptions.AcceptorOptions.() -> Unit = {}
48-
): ServerSocket = bind(selector, localAddress, options.acceptor().apply(configure))
48+
): ServerSocket = tcpBind(selector, localAddress, options.tcpAccept().apply(configure))
4949
}

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/TypeOfService.kt renamed to ktor-network/common/src/io/ktor/network/sockets/TypeOfService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3-
*/
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
44

55
package io.ktor.network.sockets
66

ktor-network/jvmAndPosix/src/io/ktor/network/sockets/UDPSocketBuilder.kt renamed to ktor-network/common/src/io/ktor/network/sockets/UDPSocketBuilder.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class UDPSocketBuilder internal constructor(
1919
public suspend fun bind(
2020
localAddress: SocketAddress? = null,
2121
configure: SocketOptions.UDPSocketOptions.() -> Unit = {}
22-
): BoundDatagramSocket = bindUDP(selector, localAddress, options.udp().apply(configure))
22+
): BoundDatagramSocket = udpBind(selector, localAddress, options.udp().apply(configure))
2323

2424
/**
2525
* Create a datagram socket to listen datagrams at [localAddress] and set to [remoteAddress].
@@ -28,18 +28,5 @@ public class UDPSocketBuilder internal constructor(
2828
remoteAddress: SocketAddress,
2929
localAddress: SocketAddress? = null,
3030
configure: SocketOptions.UDPSocketOptions.() -> Unit = {}
31-
): ConnectedDatagramSocket = connectUDP(selector, remoteAddress, localAddress, options.udp().apply(configure))
31+
): ConnectedDatagramSocket = udpConnect(selector, remoteAddress, localAddress, options.udp().apply(configure))
3232
}
33-
34-
internal expect fun connectUDP(
35-
selector: SelectorManager,
36-
remoteAddress: SocketAddress,
37-
localAddress: SocketAddress?,
38-
options: SocketOptions.UDPSocketOptions
39-
): ConnectedDatagramSocket
40-
41-
internal expect fun bindUDP(
42-
selector: SelectorManager,
43-
localAddress: SocketAddress?,
44-
options: SocketOptions.UDPSocketOptions
45-
): BoundDatagramSocket

ktor-network/jvmAndPosix/test/io/ktor/network/sockets/tests/TCPSocketTest.kt renamed to ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -62,48 +62,49 @@ class TCPSocketTest {
6262
if (!supportsUnixDomainSockets()) return@testSockets
6363

6464
val socketPath = createTempFilePath("ktor-echo-test")
65+
try {
66+
val tcp = aSocket(selector).tcp()
67+
val server = tcp.bind(UnixSocketAddress(socketPath))
6568

66-
val tcp = aSocket(selector).tcp()
67-
val server = tcp.bind(UnixSocketAddress(socketPath))
69+
val serverConnectionPromise = async {
70+
server.accept()
71+
}
6872

69-
val serverConnectionPromise = async {
70-
server.accept()
71-
}
73+
val clientConnection = tcp.connect(UnixSocketAddress(socketPath))
74+
val serverConnection = serverConnectionPromise.await()
7275

73-
val clientConnection = tcp.connect(UnixSocketAddress(socketPath))
74-
val serverConnection = serverConnectionPromise.await()
76+
val clientOutput = clientConnection.openWriteChannel()
77+
try {
78+
clientOutput.writeStringUtf8("Hello, world\n")
79+
clientOutput.flush()
80+
} finally {
81+
clientOutput.flushAndClose()
82+
}
7583

76-
val clientOutput = clientConnection.openWriteChannel()
77-
try {
78-
clientOutput.writeStringUtf8("Hello, world\n")
79-
clientOutput.flush()
80-
} finally {
81-
clientOutput.flushAndClose()
82-
}
84+
val serverInput = serverConnection.openReadChannel()
85+
val message = serverInput.readUTF8Line()
86+
assertEquals("Hello, world", message)
8387

84-
val serverInput = serverConnection.openReadChannel()
85-
val message = serverInput.readUTF8Line()
86-
assertEquals("Hello, world", message)
88+
val serverOutput = serverConnection.openWriteChannel()
89+
try {
90+
serverOutput.writeStringUtf8("Hello From Server\n")
91+
serverOutput.flush()
8792

88-
val serverOutput = serverConnection.openWriteChannel()
89-
try {
90-
serverOutput.writeStringUtf8("Hello From Server\n")
91-
serverOutput.flush()
93+
val clientInput = clientConnection.openReadChannel()
94+
val echo = clientInput.readUTF8Line()
9295

93-
val clientInput = clientConnection.openReadChannel()
94-
val echo = clientInput.readUTF8Line()
96+
assertEquals("Hello From Server", echo)
97+
} finally {
98+
serverOutput.flushAndClose()
99+
}
95100

96-
assertEquals("Hello From Server", echo)
101+
serverConnection.close()
102+
clientConnection.close()
103+
104+
server.close()
97105
} finally {
98-
serverOutput.flushAndClose()
106+
removeFile(socketPath)
99107
}
100-
101-
serverConnection.close()
102-
clientConnection.close()
103-
104-
server.close()
105-
106-
removeFile(socketPath)
107108
}
108109

109110
@Test
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.network.sockets.tests
6+
7+
import io.ktor.network.selector.*
8+
import io.ktor.test.dispatcher.*
9+
import io.ktor.utils.io.core.*
10+
import kotlinx.coroutines.*
11+
import kotlinx.coroutines.test.*
12+
import kotlinx.io.files.*
13+
import kotlin.time.*
14+
import kotlin.time.Duration.Companion.minutes
15+
import kotlin.uuid.*
16+
17+
internal fun testSockets(
18+
timeout: Duration = 1.minutes,
19+
block: suspend CoroutineScope.(SelectorManager) -> Unit
20+
): TestResult = runTestWithRealTime(timeout = timeout) {
21+
SelectorManager().use { selector ->
22+
block(selector)
23+
}
24+
}
25+
26+
internal expect fun Any.supportsUnixDomainSockets(): Boolean
27+
28+
@OptIn(ExperimentalUuidApi::class)
29+
internal fun createTempFilePath(basename: String): String {
30+
return Path(SystemTemporaryDirectory, "$basename-${Uuid.random()}").toString()
31+
}
32+
33+
internal fun removeFile(path: String) {
34+
SystemFileSystem.delete(Path(path), mustExist = false)
35+
}

ktor-network/gradle.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#
2+
# Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
#
4+
target.js.browser=false
5+
target.wasmJs.browser=false

ktor-network/ios/test/io/ktor/network/sockets/tests/TestUtilsIos.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)