Skip to content

Commit 90fb84a

Browse files
committed
Merge remote-tracking branch 'origin/main' into 3.3.0-eap
# Conflicts: # gradle.properties
2 parents 5eb912e + a314b2d commit 90fb84a

File tree

14 files changed

+226
-27
lines changed

14 files changed

+226
-27
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
# 3.2.3
2+
> Published 29 July 2025
3+
4+
### Improvements
5+
* Server only accepts `yaml` as the configuration file suffix ([KTOR-8712](https://youtrack.jetbrains.com/issue/KTOR-8712))
6+
* JS / WASM error when process global is undefined ([KTOR-8686](https://youtrack.jetbrains.com/issue/KTOR-8686))
7+
* DI async duplicate resolution ([KTOR-8681](https://youtrack.jetbrains.com/issue/KTOR-8681))
8+
9+
### Bugfixes
10+
* CIO: Expect 100-continue response is missing a final `\r\n` ([KTOR-8687](https://youtrack.jetbrains.com/issue/KTOR-8687))
11+
* Intermittent "ParserException: No colon in HTTP header" when parsing multipart request ([KTOR-8523](https://youtrack.jetbrains.com/issue/KTOR-8523))
12+
* Infinite loop in ByteReadChannel.readFully ([KTOR-8682](https://youtrack.jetbrains.com/issue/KTOR-8682))
13+
* ShutDownUrl: The server cannot shut down since 3.2.0 ([KTOR-8674](https://youtrack.jetbrains.com/issue/KTOR-8674))
14+
15+
116
# 3.2.2
217
> Published 14 July 2025
318

ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/CIOEngine.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,10 @@ internal class CIOEngine(
111111
val port: Int
112112
val protocol: URLProtocol = url.protocol
113113

114-
if (proxy != null) {
115-
val proxyAddress = proxy.resolveAddress()
114+
val actualProxy = proxy ?: lookupGlobalProxy(url)
115+
116+
if (actualProxy != null) {
117+
val proxyAddress = actualProxy.resolveAddress()
116118
host = proxyAddress.hostname
117119
port = proxyAddress.port
118120
} else {
@@ -127,7 +129,7 @@ internal class CIOEngine(
127129
Endpoint(
128130
host,
129131
port,
130-
proxy,
132+
actualProxy,
131133
secure,
132134
config,
133135
connectionFactory,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2014-2025 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.client.engine.cio
6+
7+
import io.ktor.client.engine.ProxyConfig
8+
import io.ktor.http.Url
9+
10+
internal expect fun lookupGlobalProxy(url: Url): ProxyConfig?
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2014-2025 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.client.engine.cio
6+
7+
import io.ktor.client.engine.ProxyConfig
8+
import io.ktor.http.Url
9+
import io.ktor.http.toURI
10+
import java.net.Proxy
11+
import java.net.ProxySelector
12+
import java.net.URISyntaxException
13+
14+
internal actual fun lookupGlobalProxy(url: Url): ProxyConfig? {
15+
val url = try {
16+
url.toURI()
17+
} catch (_: URISyntaxException) {
18+
null
19+
}
20+
21+
if (url == null) {
22+
return null
23+
}
24+
25+
val proxies = ProxySelector.getDefault().select(url)
26+
27+
return if (proxies.isNotEmpty()) {
28+
val proxy = proxies.first()
29+
30+
// When no proxy is available, the list will contain one element with type DIRECT
31+
if (proxies.size == 1 && proxy.type() == Proxy.Type.DIRECT) {
32+
null
33+
} else {
34+
proxy
35+
}
36+
} else {
37+
null
38+
}
39+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2014-2025 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.client.engine.cio
6+
7+
import io.ktor.client.engine.ProxyConfig
8+
import io.ktor.http.Url
9+
10+
internal actual fun lookupGlobalProxy(url: Url): ProxyConfig? = null
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2014-2025 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.client.tests
6+
7+
import io.ktor.client.call.body
8+
import io.ktor.client.engine.ProxyBuilder
9+
import io.ktor.client.request.get
10+
import io.ktor.client.test.base.*
11+
import io.ktor.http.Url
12+
import kotlin.test.Test
13+
import kotlin.test.assertEquals
14+
15+
class ProxyJvmTest : ClientLoader() {
16+
17+
@Test
18+
fun globalProxyProperty() = clientTests(only("CIO")) {
19+
val proxyUrl = Url(TCP_SERVER)
20+
System.setProperty("http.proxyHost", proxyUrl.host)
21+
System.setProperty("http.proxyPort", proxyUrl.port.toString())
22+
23+
test { client ->
24+
try {
25+
val response = client.get("http://google.com").body<String>()
26+
assertEquals("proxy", response)
27+
} finally {
28+
System.clearProperty("http.proxyHost")
29+
System.clearProperty("http.proxyPort")
30+
}
31+
}
32+
}
33+
34+
@Test
35+
fun configuredProxyHasPriorityOverGlobalOne() = clientTests(only("CIO")) {
36+
System.setProperty("http.proxyHost", "localhost")
37+
System.setProperty("http.proxyPort", "1")
38+
39+
config {
40+
engine {
41+
proxy = ProxyBuilder.http(Url(TCP_SERVER))
42+
}
43+
}
44+
45+
test { client ->
46+
try {
47+
val response = client.get("http://google.com").body<String>()
48+
assertEquals("proxy", response)
49+
} finally {
50+
System.clearProperty("http.proxyHost")
51+
System.clearProperty("http.proxyPort")
52+
}
53+
}
54+
}
55+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public class CIOApplicationEngine(
120120
handleRequest(request)
121121
}
122122
}
123+
123124
else -> {
124125
val settings = HttpServerSettings(
125126
host = connectorSpec.host,
@@ -136,7 +137,7 @@ public class CIOApplicationEngine(
136137
}
137138

138139
private fun addHandlerForExpectedHeader(output: ByteWriteChannel, call: CIOApplicationCall) {
139-
val continueResponse = "HTTP/1.1 100 Continue\r\n"
140+
val continueResponse = "HTTP/1.1 100 Continue$CRLF$CRLF"
140141
val expectHeaderValue = "100-continue"
141142

142143
val expectedHeaderPhase = PipelinePhase("ExpectedHeaderPhase")
@@ -249,3 +250,5 @@ public class CIOApplicationEngine(
249250
}
250251
}
251252
}
253+
254+
private const val CRLF = "\r\n"

ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class CIOHttpServerTest : HttpServerCommonTestSuite<CIOApplicationEngine, CIOApp
9292
writePostHeaders(writeChannel, body.length)
9393
val continueResponse = readChannel.readUTF8Line()
9494
assertEquals("HTTP/1.1 100 Continue", continueResponse)
95+
assertTrue { readChannel.readUTF8Line()?.isEmpty() ?: false }
9596

9697
writePostBody(writeChannel, body)
9798
val response = readAvailable(readChannel)

ktor-server/ktor-server-config-yaml/jvm/src/io/ktor/server/config/yaml/YamlConfigJvm.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import java.io.File
2020
public actual fun YamlConfig(path: String?): YamlConfig? {
2121
val resolvedPath = when {
2222
path == null -> DEFAULT_YAML_FILENAME
23-
path.endsWith(".yaml") -> path
23+
path.endsWith(".yaml") || path.endsWith(".yml") -> path
2424
else -> return null
2525
}
2626
val resource = Thread.currentThread().contextClassLoader.getResource(resolvedPath)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ktor:
2+
deployment:
3+
port: 2345
4+
auth:
5+
users:
6+
- c
7+
- d
8+
- e

0 commit comments

Comments
 (0)