Skip to content

Commit 1f054f7

Browse files
authored
feat: enhance proxy exceptions (#837)
1 parent a04c2cb commit 1f054f7

File tree

4 files changed

+67
-5
lines changed

4 files changed

+67
-5
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "2c77890c-7867-41d2-b0c4-6e28525989d3",
3+
"type": "feature",
4+
"description": "Enhance exceptions thrown during proxy config parsing"
5+
}

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package aws.smithy.kotlin.runtime.http.engine
77

8+
import aws.smithy.kotlin.runtime.ClientException
89
import aws.smithy.kotlin.runtime.net.Host
910
import aws.smithy.kotlin.runtime.net.Scheme
1011
import aws.smithy.kotlin.runtime.net.Url
@@ -58,17 +59,35 @@ private fun resolveProxyByProperty(provider: PropertyProvider, scheme: Scheme):
5859
// we don't support connecting to the proxy over TLS, we expect engines would support
5960
// tunneling https traffic via HTTP Connect to the proxy
6061
val proxyProtocol = Scheme.HTTP
61-
val url = Url(proxyProtocol, Host.parse(hostName), proxyPortProp?.toInt() ?: scheme.defaultPort)
62+
63+
val url = try {
64+
Url(proxyProtocol, Host.parse(hostName), proxyPortProp?.toInt() ?: scheme.defaultPort)
65+
} catch (e: Exception) {
66+
val parsed = buildString {
67+
append("""$hostPropName="$proxyHostProp"""")
68+
proxyPortProp?.let { append(""", $hostPortPropName="$it"""") }
69+
}
70+
throw ClientException("Could not parse $parsed into a valid proxy URL", e)
71+
}
72+
6273
ProxyConfig.Http(url)
6374
}
6475
}
6576

6677
private fun resolveProxyByEnvironment(provider: EnvironmentProvider, scheme: Scheme): ProxyConfig? =
6778
// lowercase takes precedence: https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/
6879
listOf("${scheme.protocolName.lowercase()}_proxy", "${scheme.protocolName.uppercase()}_PROXY")
69-
.mapNotNull { provider.getenv(it) }
70-
.map { ProxyConfig.Http(Url.parse(it)) }
71-
.firstOrNull()
80+
.firstNotNullOfOrNull { envVar ->
81+
provider.getenv(envVar)?.let { proxyUrlString ->
82+
val url = try {
83+
Url.parse(proxyUrlString)
84+
} catch (e: Exception) {
85+
val parsed = """$envVar="$proxyUrlString""""
86+
throw ClientException("Could not parse $parsed into a valid proxy URL", e)
87+
}
88+
ProxyConfig.Http(url)
89+
}
90+
}
7291

7392
internal data class NoProxyHost(val hostMatch: String, val port: Int? = null) {
7493
fun matches(url: Url): Boolean {

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
package aws.smithy.kotlin.runtime.http.engine
77

8+
import aws.smithy.kotlin.runtime.ClientException
89
import aws.smithy.kotlin.runtime.net.Url
910
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
11+
import org.junit.jupiter.api.assertThrows
1012
import kotlin.test.Test
13+
import kotlin.test.assertContains
1114
import kotlin.test.assertEquals
1215

1316
data class TestPlatformEnvironmentProvider(
@@ -77,4 +80,39 @@ class EnvironmentProxySelectorTest {
7780
assertEquals(testCase.expected, actual, "[idx=$idx] expected $testCase resulted in proxy config: $actual")
7881
}
7982
}
83+
84+
private data class FailCase(
85+
val env: Map<String, String> = emptyMap(),
86+
val props: Map<String, String> = emptyMap(),
87+
)
88+
89+
private val failCases = listOf(
90+
// Invalid ports specified
91+
FailCase(props = mapOf("http.proxyHost" to "test.proxy.aws", "http.proxyPort" to "0")),
92+
FailCase(props = mapOf("http.proxyHost" to "test.proxy.aws", "http.proxyPort" to "x")),
93+
FailCase(props = mapOf("https.proxyHost" to "test.proxy.aws", "https.proxyPort" to "0")),
94+
FailCase(props = mapOf("https.proxyHost" to "test.proxy.aws", "https.proxyPort" to "x")),
95+
FailCase(env = mapOf("http_proxy" to "http://test.proxy.aws:0")),
96+
FailCase(env = mapOf("http_proxy" to "http://test.proxy.aws:x")),
97+
FailCase(env = mapOf("https_proxy" to "https://test.proxy.aws:0")),
98+
FailCase(env = mapOf("https_proxy" to "https://test.proxy.aws:x")),
99+
)
100+
101+
@Test
102+
fun testSelectFailures() {
103+
failCases.forEachIndexed { idx, failCase ->
104+
val testProvider = TestPlatformEnvironmentProvider(failCase.env, failCase.props)
105+
val exception = assertThrows<ClientException>("[idx=$idx] expected ClientException") {
106+
EnvironmentProxySelector(testProvider)
107+
}
108+
109+
val expectedError = (failCase.env + failCase.props).map { (k, v) -> """$k="$v"""" }.joinToString(", ")
110+
111+
assertContains(
112+
exception.message!!,
113+
expectedError,
114+
message = "[idx=$idx] unexpected error message ${exception.message}",
115+
)
116+
}
117+
}
80118
}

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public data class Url(
3535
public val encodeParameters: Boolean = true,
3636
) {
3737
init {
38-
require(port in 1..65535) { "port must be in range [1, 65535]" }
38+
require(port in 1..65535) { "Given port $port is not in required range [1, 65535]" }
3939
}
4040

4141
public companion object {

0 commit comments

Comments
 (0)