Skip to content

Commit ae8667d

Browse files
committed
KTOR-8208 Java: Make webSocket field nullable
1 parent 8b73519 commit ae8667d

File tree

2 files changed

+47
-30
lines changed

2 files changed

+47
-30
lines changed

ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/JavaHttpWebSocket.kt

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ internal class JavaHttpWebSocket(
6464
private val requestTime: GMTDate = GMTDate()
6565
) : WebSocket.Listener, WebSocketSession {
6666

67-
private lateinit var webSocket: WebSocket
67+
private var _webSocket: WebSocket? = null
68+
private val webSocket: WebSocket
69+
get() = checkNotNull(_webSocket) { "Web socket is not connected yet." }
70+
6871
private val socketJob = Job(callContext[Job])
6972
private val _incoming = Channel<Frame>(Channel.UNLIMITED)
7073
private val _outgoing = Channel<Frame>(Channel.UNLIMITED)
@@ -90,50 +93,45 @@ internal class JavaHttpWebSocket(
9093
get() = emptyList()
9194

9295
init {
93-
launch {
96+
launch(CoroutineName("java-ws-outgoing")) {
9497
_outgoing.consumeEach { frame ->
95-
when (frame.frameType) {
96-
FrameType.TEXT -> {
97-
webSocket.sendText(String(frame.data), frame.fin).await()
98-
}
99-
100-
FrameType.BINARY -> {
101-
webSocket.sendBinary(frame.buffer, frame.fin).await()
102-
}
103-
104-
FrameType.CLOSE -> {
105-
val data = buildPacket { writeFully(frame.data) }
106-
val code = data.readShort().toInt()
107-
val reason = data.readText()
108-
webSocket.sendClose(code, reason).await()
109-
socketJob.complete()
110-
return@launch
111-
}
112-
113-
FrameType.PING -> {
114-
webSocket.sendPing(frame.buffer).await()
115-
}
116-
117-
FrameType.PONG -> {
118-
webSocket.sendPong(frame.buffer).await()
119-
}
98+
webSocket.sendFrame(frame)
99+
if (frame.frameType == FrameType.CLOSE) {
100+
socketJob.complete()
101+
return@launch
120102
}
121103
}
122104
}
123105

124-
GlobalScope.launch(callContext, start = CoroutineStart.ATOMIC) {
106+
GlobalScope.launch(callContext + CoroutineName("java-ws-closer"), start = CoroutineStart.ATOMIC) {
125107
try {
126108
socketJob[Job]!!.join()
127109
} catch (cause: Throwable) {
128110
val code = CloseReason.Codes.INTERNAL_ERROR.code.toInt()
129-
webSocket.sendClose(code, "Client failed")
111+
_webSocket?.sendClose(code, "Client failed")
130112
} finally {
131113
_incoming.close()
132114
_outgoing.cancel()
133115
}
134116
}
135117
}
136118

119+
private suspend fun WebSocket.sendFrame(frame: Frame) {
120+
when (frame.frameType) {
121+
FrameType.TEXT -> sendText(String(frame.data), frame.fin).await()
122+
FrameType.BINARY -> sendBinary(frame.buffer, frame.fin).await()
123+
FrameType.PING -> sendPing(frame.buffer).await()
124+
FrameType.PONG -> sendPong(frame.buffer).await()
125+
126+
FrameType.CLOSE -> {
127+
val data = buildPacket { writeFully(frame.data) }
128+
val code = data.readShort().toInt()
129+
val reason = data.readText()
130+
sendClose(code, reason).await()
131+
}
132+
}
133+
}
134+
137135
@OptIn(InternalAPI::class)
138136
suspend fun getResponse(): HttpResponseData {
139137
val builder = httpClient.newWebSocketBuilder()
@@ -163,7 +161,7 @@ internal class JavaHttpWebSocket(
163161
var status = HttpStatusCode.SwitchingProtocols
164162
var headers: Headers
165163
try {
166-
webSocket = builder.buildAsync(requestData.url.toURI(), this).await()
164+
_webSocket = builder.buildAsync(requestData.url.toURI(), this).await()
167165
val protocol = webSocket.subprotocol?.takeIf { it.isNotEmpty() }
168166
headers = if (protocol != null) headersOf(HttpHeaders.SecWebSocketProtocol, protocol) else Headers.Empty
169167
} catch (cause: WebSocketHandshakeException) {

ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/WebSocketTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import io.ktor.util.reflect.*
1818
import io.ktor.utils.io.charsets.*
1919
import io.ktor.websocket.*
2020
import kotlinx.coroutines.*
21+
import kotlinx.io.IOException
2122
import kotlin.test.*
2223
import kotlin.time.Duration.Companion.seconds
2324

@@ -243,6 +244,24 @@ class WebSocketTest : ClientLoader() {
243244
}
244245
}
245246

247+
@Test
248+
fun testConnectionTimeoutExceeded() = clientTests(except(ENGINES_WITHOUT_WS)) {
249+
val nonExistingHost = "ws://192.0.2.0" // RFC 5737: TEST-NET-1
250+
251+
config {
252+
install(WebSockets)
253+
install(HttpTimeout) { connectTimeoutMillis = 1 }
254+
}
255+
256+
test { client ->
257+
val exception = assertFailsWith<IOException> {
258+
client.webSocket(nonExistingHost) { fail("Shouldn't be reached") }
259+
}
260+
261+
assertTrue(exception.suppressedExceptions.isEmpty(), "No exception should be suppressed")
262+
}
263+
}
264+
246265
@Test
247266
fun testCountPong() = clientTests(except(ENGINES_WITHOUT_WS + "Js")) {
248267
config {

0 commit comments

Comments
 (0)