Skip to content

Commit 828c18d

Browse files
committed
fixup! KTOR-6734 Fixes for Jetty 12 upgrade
1 parent d014b91 commit 828c18d

File tree

11 files changed

+151
-255
lines changed

11 files changed

+151
-255
lines changed

ktor-http/common/src/io/ktor/http/Query.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.ktor.http
66

7+
import io.ktor.utils.io.InternalAPI
8+
79
/**
810
* Parse query string withing starting at the specified [startIndex] but up to [limit] pairs
911
*
@@ -94,3 +96,24 @@ private fun trimStart(start: Int, end: Int, query: CharSequence): Int {
9496
while (spaceIndex < end && query[spaceIndex].isWhitespace()) spaceIndex++
9597
return spaceIndex
9698
}
99+
100+
/**
101+
* Converts parameters to query parameters by fixing the [Parameters.get] method
102+
* to make it return an empty string for the query parameter without value
103+
*/
104+
@InternalAPI
105+
public fun Parameters.toQueryParameters(): Parameters {
106+
val parameters = this
107+
return object : Parameters {
108+
override fun get(name: String): String? {
109+
val values = getAll(name) ?: return null
110+
return if (values.isEmpty()) "" else values.first()
111+
}
112+
override val caseInsensitiveName: Boolean
113+
get() = parameters.caseInsensitiveName
114+
override fun getAll(name: String): List<String>? = parameters.getAll(name)
115+
override fun names(): Set<String> = parameters.names()
116+
override fun entries(): Set<Map.Entry<String, List<String>>> = parameters.entries()
117+
override fun isEmpty(): Boolean = parameters.isEmpty()
118+
}
119+
}

ktor-server/ktor-server-cio/common/src/io/ktor/server/cio/CIOApplicationRequest.kt

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal class CIOApplicationRequest(
2525

2626
override var engineHeaders: Headers = CIOHeaders(request.headers)
2727

28+
@OptIn(InternalAPI::class)
2829
override val queryParameters: Parameters by lazy { encodeParameters(rawQueryParameters).toQueryParameters() }
2930

3031
override val rawQueryParameters: Parameters by lazy {
@@ -47,26 +48,6 @@ internal class CIOApplicationRequest(
4748
}
4849
}
4950

50-
/**
51-
* Converts parameters to query parameters by fixing the [Parameters.get] method
52-
* to make it return an empty string for the query parameter without value
53-
*/
54-
private fun Parameters.toQueryParameters(): Parameters {
55-
val parameters = this
56-
return object : Parameters {
57-
override fun get(name: String): String? {
58-
val values = getAll(name) ?: return null
59-
return if (values.isEmpty()) "" else values.first()
60-
}
61-
override val caseInsensitiveName: Boolean
62-
get() = parameters.caseInsensitiveName
63-
override fun getAll(name: String): List<String>? = parameters.getAll(name)
64-
override fun names(): Set<String> = parameters.names()
65-
override fun entries(): Set<Map.Entry<String, List<String>>> = parameters.entries()
66-
override fun isEmpty(): Boolean = parameters.isEmpty()
67-
}
68-
}
69-
7051
internal class CIOConnectionPoint(
7152
private val remoteNetworkAddress: NetworkAddress?,
7253
private val localNetworkAddress: NetworkAddress?,

ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyApplicationRequest.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ public class JettyApplicationRequest(
2828
call.writer(Dispatchers.IO + CoroutineName("request-reader")) {
2929
val contentLength = if (request.headers.contains(HttpHeaders.ContentLength)) {
3030
request.headers.get(HttpHeaders.ContentLength)?.toLong()
31-
} else null
31+
} else {
32+
null
33+
}
3234

3335
var bytesRead = 0L
3436
while (true) {
35-
when(val chunk = request.read()) {
37+
when (val chunk = request.read()) {
3638
// nothing available, suspend for more content
3739
null -> {
3840
suspendCancellableCoroutine { continuation ->
@@ -43,8 +45,9 @@ public class JettyApplicationRequest(
4345
else -> {
4446
with(chunk) {
4547
if (failure != null) {
46-
if (isLast)
48+
if (isLast) {
4749
throw failure
50+
}
4851
call.application.log.warn("Recoverable error reading request body; continuing", failure)
4952
} else {
5053
bytesRead += byteBuffer.remaining()
@@ -55,7 +58,9 @@ public class JettyApplicationRequest(
5558
}
5659
if (isLast) {
5760
if (contentLength != null && bytesRead < contentLength) {
58-
channel.cancel(EOFException("Expected $contentLength bytes, received only $bytesRead"))
61+
channel.cancel(
62+
EOFException("Expected $contentLength bytes, received only $bytesRead")
63+
)
5964
}
6065
return@writer
6166
}
@@ -75,7 +80,7 @@ public class JettyApplicationRequest(
7580
override val local: RequestConnectionPoint = JettyConnectionPoint(request)
7681

7782
override val queryParameters: Parameters by lazy {
78-
encodeParameters(rawQueryParameters)
83+
encodeParameters(rawQueryParameters).toQueryParameters()
7984
}
8085

8186
override val rawQueryParameters: Parameters by lazy(LazyThreadSafetyMode.NONE) {
@@ -109,7 +114,9 @@ public class JettyHeaders(
109114
}.toSet()
110115
}
111116

112-
override fun getAll(name: String): List<String>? = jettyRequest.headers.getValuesList(name).takeIf { it.isNotEmpty() }
117+
override fun getAll(name: String): List<String>? = jettyRequest.headers.getValuesList(name).takeIf {
118+
it.isNotEmpty()
119+
}
113120

114121
override fun get(name: String): String? = jettyRequest.headers.get(name).takeIf { it.isNotEmpty() }
115122

ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyApplicationResponse.kt

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.ktor.http.*
88
import io.ktor.http.content.*
99
import io.ktor.server.application.*
1010
import io.ktor.server.engine.*
11+
import io.ktor.server.jetty.jakarta.JettyWebsocketConnection.Companion.upgradeAndAwait
1112
import io.ktor.server.response.*
1213
import io.ktor.utils.io.*
1314
import io.ktor.utils.io.pool.*
@@ -70,8 +71,9 @@ public class JettyApplicationResponse(
7071
} finally {
7172
bufferPool.recycle(buffer)
7273
runCatching {
73-
if (!response.isCommitted)
74+
if (!response.isCommitted) {
7475
response.write(true, emptyBuffer, Callback.NOOP)
76+
}
7577
}
7678
}
7779
}
@@ -82,45 +84,41 @@ public class JettyApplicationResponse(
8284
response.headers.add(name, value)
8385
}
8486

85-
override fun getEngineHeaderNames(): List<String> = response.headers.fieldNamesCollection.toList()
86-
override fun getEngineHeaderValues(name: String): List<String> = response.headers.getValuesList(name)
87+
override fun getEngineHeaderNames(): List<String> =
88+
response.headers.fieldNamesCollection.toList()
89+
90+
override fun getEngineHeaderValues(name: String): List<String> =
91+
response.headers.getValuesList(name)
8792
}
8893

8994
// TODO set idle timeout from websocket config on endpoint
9095
override suspend fun respondUpgrade(upgrade: OutgoingContent.ProtocolUpgrade) {
91-
if (responseJob.isInitialized())
96+
if (responseJob.isInitialized()) {
9297
responseJob.value.cancel()
98+
}
9399

94-
// use the underlying endpoint instance for two-way connection
100+
// Use the underlying endpoint instance for two-way connection
95101
val endpoint = request.connectionMetaData.connection.endPoint
96102
endpoint.idleTimeout = 6000 * 1000
97103

98-
val websocketConnection = JettyWebsocketConnection2(
104+
// An [AbstractConnection] implementation for translating I/O
105+
val websocketConnection = JettyWebsocketConnection(
99106
endpoint,
100107
executor,
101108
bufferPool,
102109
coroutineContext
103110
)
104111

112+
// Finish the current response channel by writing an empty message with last=true
105113
suspendCancellableCoroutine { continuation ->
106114
response.write(true, emptyBuffer, continuation.asCallback())
107115
}
108116

109-
endpoint.upgrade(websocketConnection)
110-
111-
val upgradeJob = upgrade.upgrade(
112-
websocketConnection.inputChannel,
113-
websocketConnection.outputChannel,
114-
coroutineContext,
115-
userContext,
117+
// Start a job for handling the websocket connection and wait for it to finish
118+
upgrade.upgradeAndAwait(
119+
websocketConnection,
120+
userContext
116121
)
117-
118-
upgradeJob.invokeOnCompletion {
119-
websocketConnection.inputChannel.cancel()
120-
websocketConnection.outputChannel.close()
121-
}
122-
123-
upgradeJob.join()
124122
}
125123

126124
override suspend fun respondFromBytes(bytes: ByteArray) {

ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyKtorHandler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ internal class JettyKtorHandler(
7272
try {
7373
logError(call, error)
7474
if (!response.isCommitted) {
75-
Response.writeError(request,
75+
Response.writeError(
76+
request,
7677
response,
7778
callback,
7879
HttpStatus.INTERNAL_SERVER_ERROR_500,

0 commit comments

Comments
 (0)