Skip to content

Commit f27a8a3

Browse files
committed
Fix NoClassDefFoundError on Android due to JDK11's WebSocketHandshakeException
Resolves: #246
1 parent 98367c1 commit f27a8a3

File tree

2 files changed

+98
-12
lines changed

2 files changed

+98
-12
lines changed

krossbow-websocket-ktor-legacy/src/jvmMain/kotlin/org/hildan/krossbow/websocket/ktor/HandshakeExceptionsJvm.kt

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ package org.hildan.krossbow.websocket.ktor
33
import java.net.ProtocolException
44
import java.net.UnknownHostException
55
import java.net.http.WebSocketHandshakeException
6+
import kotlin.contracts.ExperimentalContracts
7+
import kotlin.contracts.contract
68

7-
internal actual fun extractHandshakeStatusCode(handshakeException: Exception): Int? = when (handshakeException) {
8-
is WebSocketHandshakeException -> handshakeException.response.statusCode()
9-
// with OkHttp engine, we get ProtocolException with itself as cause - we can only parse the message
10-
is ProtocolException -> extractHandshakeStatusCode(handshakeException)
11-
is UnknownHostException -> null
12-
else -> null
9+
internal actual fun extractHandshakeStatusCode(handshakeException: Exception): Int? {
10+
val e = handshakeException
11+
return when {
12+
// no status code if we can't even contact the host
13+
e is UnknownHostException -> null
14+
// with OkHttp engine, we get ProtocolException with itself as cause - we can only parse the message
15+
e is ProtocolException -> extractHandshakeStatusCode(e)
16+
e.safeIs<WebSocketHandshakeException>("java.net.http.WebSocketHandshakeException") -> {
17+
extractHandshakeStatusCode(e)
18+
}
19+
else -> null
20+
}
1321
}
1422

1523
private val protocolExceptionMessageRegex = Regex("""Expected HTTP 101 response but was '(\d{3}) [^']+'""")
@@ -18,3 +26,38 @@ private fun extractHandshakeStatusCode(handshakeException: ProtocolException): I
1826
val message = handshakeException.message ?: return null
1927
return protocolExceptionMessageRegex.matchEntire(message)?.groupValues?.get(1)?.toInt()
2028
}
29+
30+
private fun extractHandshakeStatusCode(webSocketHandshakeException: WebSocketHandshakeException) =
31+
webSocketHandshakeException.response.statusCode()
32+
33+
/**
34+
* Returns true if [C] is on the classpath and `this` is an instance of [C].
35+
* The given [className] must match the fully qualified name of [C].
36+
*
37+
* Doesn't fail with [NoClassDefFoundError] if [C] is not present.
38+
*/
39+
@OptIn(ExperimentalContracts::class)
40+
private inline fun <reified C : Any> Any.safeIs(className: String): Boolean {
41+
contract {
42+
returns(true) implies(this@safeIs is C)
43+
}
44+
if (!classExists(className)) {
45+
return false
46+
}
47+
checkMatches<C>(className) // prevent developer mistakes
48+
return this is C
49+
}
50+
51+
private inline fun <reified T : Any> checkMatches(className: String) {
52+
val typeName = T::class.qualifiedName
53+
require(typeName == className) {
54+
"Mismatch between the given class name '$className' and the actual parameter type of the action lambda '$typeName"
55+
}
56+
}
57+
58+
private fun classExists(className: String): Boolean = try {
59+
Class.forName(className)
60+
true
61+
} catch (e: ClassNotFoundException) {
62+
false
63+
}

krossbow-websocket-ktor/src/jvmMain/kotlin/org/hildan/krossbow/websocket/ktor/HandshakeExceptionsJvm.kt

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ package org.hildan.krossbow.websocket.ktor
33
import java.net.ProtocolException
44
import java.net.UnknownHostException
55
import java.net.http.WebSocketHandshakeException
6+
import kotlin.contracts.ExperimentalContracts
7+
import kotlin.contracts.contract
68

7-
internal actual fun extractHandshakeStatusCode(handshakeException: Exception): Int? = when (handshakeException) {
8-
is WebSocketHandshakeException -> handshakeException.response.statusCode()
9-
// with OkHttp engine, we get ProtocolException with itself as cause - we can only parse the message
10-
is ProtocolException -> extractHandshakeStatusCode(handshakeException)
11-
is UnknownHostException -> null
12-
else -> null
9+
internal actual fun extractHandshakeStatusCode(handshakeException: Exception): Int? {
10+
val e = handshakeException
11+
return when {
12+
// no status code if we can't even contact the host
13+
e is UnknownHostException -> null
14+
// with OkHttp engine, we get ProtocolException with itself as cause - we can only parse the message
15+
e is ProtocolException -> extractHandshakeStatusCode(e)
16+
e.safeIs<WebSocketHandshakeException>("java.net.http.WebSocketHandshakeException") -> {
17+
extractHandshakeStatusCode(e)
18+
}
19+
else -> null
20+
}
1321
}
1422

1523
private val protocolExceptionMessageRegex = Regex("""Expected HTTP 101 response but was '(\d{3}) [^']+'""")
@@ -18,3 +26,38 @@ private fun extractHandshakeStatusCode(handshakeException: ProtocolException): I
1826
val message = handshakeException.message ?: return null
1927
return protocolExceptionMessageRegex.matchEntire(message)?.groupValues?.get(1)?.toInt()
2028
}
29+
30+
private fun extractHandshakeStatusCode(webSocketHandshakeException: WebSocketHandshakeException) =
31+
webSocketHandshakeException.response.statusCode()
32+
33+
/**
34+
* Returns true if [C] is on the classpath and `this` is an instance of [C].
35+
* The given [className] must match the fully qualified name of [C].
36+
*
37+
* Doesn't fail with [NoClassDefFoundError] if [C] is not present.
38+
*/
39+
@OptIn(ExperimentalContracts::class)
40+
private inline fun <reified C : Any> Any.safeIs(className: String): Boolean {
41+
contract {
42+
returns(true) implies(this@safeIs is C)
43+
}
44+
if (!classExists(className)) {
45+
return false
46+
}
47+
checkMatches<C>(className) // prevent developer mistakes
48+
return this is C
49+
}
50+
51+
private inline fun <reified T : Any> checkMatches(className: String) {
52+
val typeName = T::class.qualifiedName
53+
require(typeName == className) {
54+
"Mismatch between the given class name '$className' and the actual parameter type of the action lambda '$typeName"
55+
}
56+
}
57+
58+
private fun classExists(className: String): Boolean = try {
59+
Class.forName(className)
60+
true
61+
} catch (e: ClassNotFoundException) {
62+
false
63+
}

0 commit comments

Comments
 (0)