Skip to content

Commit cb60d46

Browse files
authored
fix: support http.nonProxyHost JVM system property (#1083)
1 parent 74c782c commit cb60d46

File tree

5 files changed

+40
-14
lines changed

5 files changed

+40
-14
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "040b1587-ec85-4601-9eec-a5158cb0edac",
3+
"type": "bugfix",
4+
"description": "Support `http.nonProxyHosts` JVM system property",
5+
"issues": [
6+
"https://github.com/smithy-lang/smithy-kotlin/issues/1081"
7+
]
8+
}

runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelector.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import aws.smithy.kotlin.runtime.util.PropertyProvider
2222
* - `http.proxyPort`
2323
* - `https.proxyHost`
2424
* - `https.proxyPort`
25+
* - `http.nonProxyHosts`
2526
* - `http.noProxyHosts`
2627
*
2728
* **Environment variables in the given order**:
@@ -32,10 +33,10 @@ import aws.smithy.kotlin.runtime.util.PropertyProvider
3233
internal class EnvironmentProxySelector(provider: PlatformEnvironProvider = PlatformProvider.System) : ProxySelector {
3334
private val httpProxy by lazy { resolveProxyByProperty(provider, Scheme.HTTP) ?: resolveProxyByEnvironment(provider, Scheme.HTTP) }
3435
private val httpsProxy by lazy { resolveProxyByProperty(provider, Scheme.HTTPS) ?: resolveProxyByEnvironment(provider, Scheme.HTTPS) }
35-
private val noProxyHosts by lazy { resolveNoProxyHosts(provider) }
36+
private val nonProxyHosts by lazy { resolveNonProxyHosts(provider) }
3637

3738
override fun select(url: Url): ProxyConfig {
38-
if (httpProxy == null && httpsProxy == null || noProxy(url)) return ProxyConfig.Direct
39+
if (httpProxy == null && httpsProxy == null || nonProxy(url)) return ProxyConfig.Direct
3940

4041
val proxyConfig = when (url.scheme) {
4142
Scheme.HTTP -> httpProxy
@@ -46,7 +47,7 @@ internal class EnvironmentProxySelector(provider: PlatformEnvironProvider = Plat
4647
return proxyConfig ?: ProxyConfig.Direct
4748
}
4849

49-
private fun noProxy(url: Url): Boolean = noProxyHosts.any { it.matches(url) }
50+
private fun nonProxy(url: Url): Boolean = nonProxyHosts.any { it.matches(url) }
5051
}
5152

5253
private fun resolveProxyByProperty(provider: PropertyProvider, scheme: Scheme): ProxyConfig? {
@@ -94,7 +95,7 @@ private fun resolveProxyByEnvironment(provider: EnvironmentProvider, scheme: Sch
9495
}
9596
}
9697

97-
internal data class NoProxyHost(val hostMatch: String, val port: Int? = null) {
98+
internal data class NonProxyHost(val hostMatch: String, val port: Int? = null) {
9899
fun matches(url: Url): Boolean {
99100
// any host
100101
if (hostMatch == "*") return true
@@ -115,24 +116,29 @@ internal data class NoProxyHost(val hostMatch: String, val port: Int? = null) {
115116
}
116117
}
117118

118-
private fun parseNoProxyHost(raw: String): NoProxyHost {
119+
private fun parseNonProxyHost(raw: String): NonProxyHost {
119120
val pair = raw.split(':', limit = 2)
120121
return when (pair.size) {
121-
1 -> NoProxyHost(pair[0])
122-
2 -> NoProxyHost(pair[0], pair[1].toInt())
123-
else -> error("invalid no proxy host: $raw")
122+
1 -> NonProxyHost(pair[0])
123+
2 -> NonProxyHost(pair[0], pair[1].toInt())
124+
else -> error("invalid non proxy host: $raw")
124125
}
125126
}
126127

127-
private fun resolveNoProxyHosts(provider: PlatformEnvironProvider): Set<NoProxyHost> {
128+
private fun resolveNonProxyHosts(provider: PlatformEnvironProvider): Set<NonProxyHost> {
128129
// http.nonProxyHosts:a list of hosts that should be reached directly, bypassing the proxy. This is a list of
129130
// patterns separated by '|'. The patterns may start or end with a '*' for wildcards. Any host matching one of
130131
// these patterns will be reached through a direct connection instead of through a proxy.
131-
val noProxyHostProps = provider.getProperty("http.noProxyHosts")
132+
133+
// NOTE: Both http.nonProxyHosts (correct value according to the spec) AND http.noProxyHosts (legacy behavior) are checked
134+
// https://github.com/smithy-lang/smithy-kotlin/issues/1081
135+
val nonProxyHostProperty = provider.getProperty("http.nonProxyHosts") ?: provider.getProperty("http.noProxyHosts")
136+
137+
val nonProxyHostProps = nonProxyHostProperty
132138
?.split('|')
133139
?.map { it.trim() }
134140
?.map { it.trimStart('.') }
135-
?.map(::parseNoProxyHost)
141+
?.map(::parseNonProxyHost)
136142
?.toSet() ?: emptySet()
137143

138144
// `no_proxy` is a comma or space-separated list of machine or domain names, with optional :port part.
@@ -142,8 +148,8 @@ private fun resolveNoProxyHosts(provider: PlatformEnvironProvider): Set<NoProxyH
142148
.flatMap { it.split(',', ' ') }
143149
.map { it.trim() }
144150
.map { it.trimStart('.') }
145-
.map(::parseNoProxyHost)
151+
.map(::parseNonProxyHost)
146152
.toSet()
147153

148-
return noProxyHostProps + noProxyEnv
154+
return nonProxyHostProps + noProxyEnv
149155
}

runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public interface HttpClientEngineConfig {
144144
* - `http.proxyPort`
145145
* - `https.proxyHost`
146146
* - `https.proxyPort`
147+
* - `http.nonProxyHosts`
147148
* - `http.noProxyHosts`
148149
*
149150
* **Environment variables in the given order**:

runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelectorTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,17 @@ class EnvironmentProxySelectorTest {
5151
// no proxy
5252
TestCase(ProxyConfig.Direct, env = mapOf("no_proxy" to "aws.amazon.com") + httpsProxyEnv),
5353
TestCase(ProxyConfig.Direct, env = mapOf("no_proxy" to ".amazon.com") + httpsProxyEnv),
54+
5455
TestCase(ProxyConfig.Direct, props = mapOf("http.noProxyHosts" to "aws.amazon.com") + httpsProxyProps),
5556
TestCase(ProxyConfig.Direct, props = mapOf("http.noProxyHosts" to ".amazon.com") + httpsProxyProps),
5657

58+
TestCase(ProxyConfig.Direct, props = mapOf("http.nonProxyHosts" to "aws.amazon.com") + httpsProxyProps),
59+
TestCase(ProxyConfig.Direct, props = mapOf("http.nonProxyHosts" to ".amazon.com") + httpsProxyProps),
60+
5761
// multiple no proxy hosts normalization
5862
TestCase(ProxyConfig.Direct, env = mapOf("no_proxy" to "example.com,.amazon.com") + httpsProxyEnv),
5963
TestCase(ProxyConfig.Direct, props = mapOf("http.noProxyHosts" to "example.com|.amazon.com") + httpsProxyProps),
64+
TestCase(ProxyConfig.Direct, props = mapOf("http.nonProxyHosts" to "example.com|.amazon.com") + httpsProxyProps),
6065

6166
// environment
6267
TestCase(expectedProxyConfig, env = httpsProxyEnv),
@@ -68,6 +73,12 @@ class EnvironmentProxySelectorTest {
6873

6974
// no_proxy set but doesn't match
7075
TestCase(expectedProxyConfig, env = httpsProxyEnv + mapOf("no_proxy" to "example.com")),
76+
77+
// prioritize http.nonProxyHosts over http.noProxyHosts
78+
TestCase(ProxyConfig.Direct, props = mapOf("http.nonProxyHosts" to "example.com|.amazon.com", "http.noProxyHosts" to "") + httpsProxyProps),
79+
80+
// even though http.noProxyHosts is configured to go ProxyConfig.Direct, nonProxyHosts is present and should be prioritized
81+
TestCase(expectedProxyConfig, props = mapOf("http.nonProxyHosts" to "", "http.noProxyHosts" to "example.com|.amazon.com") + httpsProxyProps),
7182
)
7283

7384
@Test

runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/NoProxyHostTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class NoProxyHostTest {
4545
@Test
4646
fun testMatches() {
4747
tests.forEachIndexed { i, testCase ->
48-
val noProxyHost = NoProxyHost(testCase.noProxyHost, testCase.noProxyPort)
48+
val noProxyHost = NonProxyHost(testCase.noProxyHost, testCase.noProxyPort)
4949
val url = Url.parse(testCase.url)
5050

5151
assertEquals(testCase.shouldMatch, noProxyHost.matches(url), "[idx=$i] expected $noProxyHost to match $url")

0 commit comments

Comments
 (0)