diff --git a/README.md b/README.md index 7c7e65356..55a659f1b 100644 --- a/README.md +++ b/README.md @@ -159,8 +159,8 @@ The following table lists all valid connection properties: | TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | | INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | | USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYHOST | No | The hostname of the proxy server.

If USEPROXY is set to `true`, you can set this parameter to define the proxy explicitly. If not set, the default proxy will be used (HTTPS_PROXY, WinINet...).

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true` and PROXYHOST is specfied, you must set this parameter.

This parameter was introduced in v2.0.4. | | PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | | PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | | NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.
The host target value should fully match with any item from the proxy host list to bypass the proxy server.

This parameter was introduced in v2.0.4. | diff --git a/Snowflake.Data.Tests/SFBaseTest.cs b/Snowflake.Data.Tests/SFBaseTest.cs index 6aacb94f9..711960007 100755 --- a/Snowflake.Data.Tests/SFBaseTest.cs +++ b/Snowflake.Data.Tests/SFBaseTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ diff --git a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs index 668db5f91..911716a91 100644 --- a/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs +++ b/Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2022 Snowflake Computing Inc. All rights reserved. */ @@ -102,48 +102,75 @@ public void TestGetJitter(int seconds) } [Test] - public void ShouldCreateHttpClientHandlerWithProxy() + public void ShouldCreateHttpClientHandlerWithExplicitProxy() { // given var config = new HttpClientConfig( + true, true, "snowflake.com", "123", "testUser", "proxyPassword", - "localhost", + "localhost", false, false, 7 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + // then Assert.IsTrue(handler.UseProxy); Assert.IsNotNull(handler.Proxy); } [Test] - public void ShouldCreateHttpClientHandlerWithoutProxy() + public void ShouldCreateHttpClientHandlerWithImplicitProxy() { // given var config = new HttpClientConfig( + true, true, null, null, null, null, - null, + null, + false, + false, + 7 + ); + + // when + var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); + + // then + Assert.IsTrue(handler.UseProxy); + Assert.IsNull(handler.Proxy); + } + + [Test] + public void ShouldCreateHttpClientHandlerWithoutProxy() + { + // given + var config = new HttpClientConfig( + false, + false, + null, + null, + null, + null, + null, false, false, 0 ); - + // when var handler = (HttpClientHandler) HttpUtil.Instance.SetupCustomHttpHandler(config); - + // then Assert.IsFalse(handler.UseProxy); Assert.IsNull(handler.Proxy); diff --git a/Snowflake.Data/Core/HttpUtil.cs b/Snowflake.Data/Core/HttpUtil.cs index 531e76fd7..f7a2cd2f9 100755 --- a/Snowflake.Data/Core/HttpUtil.cs +++ b/Snowflake.Data/Core/HttpUtil.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ @@ -21,6 +21,7 @@ public class HttpClientConfig { public HttpClientConfig( bool crlCheckEnabled, + bool useProxy, string proxyHost, string proxyPort, string proxyUser, @@ -32,6 +33,7 @@ public HttpClientConfig( bool includeRetryReason = true) { CrlCheckEnabled = crlCheckEnabled; + UseProxy = useProxy; ProxyHost = proxyHost; ProxyPort = proxyPort; ProxyUser = proxyUser; @@ -45,6 +47,7 @@ public HttpClientConfig( ConfKey = string.Join(";", new string[] { crlCheckEnabled.ToString(), + useProxy.ToString(), proxyHost, proxyPort, proxyUser, @@ -57,6 +60,7 @@ public HttpClientConfig( } public readonly bool CrlCheckEnabled; + public readonly bool UseProxy; public readonly string ProxyHost; public readonly string ProxyPort; public readonly string ProxyUser; @@ -87,7 +91,7 @@ public sealed class HttpUtil private HttpUtil() { - // This value is used by AWS SDK and can cause deadlock, + // This value is used by AWS SDK and can cause deadlock, // so we need to increase the default value of 2 // See: https://github.com/aws/aws-sdk-net/issues/152 ServicePointManager.DefaultConnectionLimit = 50; @@ -130,7 +134,7 @@ private HttpClient RegisterNewHttpClientIfNecessary(HttpClientConfig config) internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) { - HttpMessageHandler httpHandler; + HttpClientHandler httpHandler; try { httpHandler = new HttpClientHandler() @@ -140,8 +144,7 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) // Enforce tls v1.2 SslProtocols = SslProtocols.Tls12, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } // special logic for .NET framework 4.7.1 that @@ -151,56 +154,62 @@ internal HttpMessageHandler SetupCustomHttpHandler(HttpClientConfig config) httpHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = false, // Disable cookies - UseProxy = false + UseCookies = false // Disable cookies }; } // Add a proxy if necessary - if (null != config.ProxyHost) + if (!config.UseProxy) + { + httpHandler.UseProxy = false; + } + else { // Proxy needed - WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); + httpHandler.UseProxy = true; - // Add credential if provided - if (!String.IsNullOrEmpty(config.ProxyUser)) + if (!String.IsNullOrEmpty(config.ProxyHost)) { - ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); - proxy.Credentials = credentials; - } + // Host explicitly specified, do not use default proxy + WebProxy proxy = new WebProxy(config.ProxyHost, int.Parse(config.ProxyPort)); - // Add bypasslist if provided - if (!String.IsNullOrEmpty(config.NoProxyList)) - { - string[] bypassList = config.NoProxyList.Split( - new char[] { '|' }, - StringSplitOptions.RemoveEmptyEntries); - // Convert simplified syntax to standard regular expression syntax - string entry = null; - for (int i = 0; i < bypassList.Length; i++) + // Add credential if provided + if (!String.IsNullOrEmpty(config.ProxyUser)) + { + ICredentials credentials = new NetworkCredential(config.ProxyUser, config.ProxyPassword); + proxy.Credentials = credentials; + } + + // Add bypasslist if provided + if (!String.IsNullOrEmpty(config.NoProxyList)) { - // Get the original entry - entry = bypassList[i].Trim(); - // . -> [.] because . means any char - entry = entry.Replace(".", "[.]"); - // * -> .* because * is a quantifier and need a char or group to apply to - entry = entry.Replace("*", ".*"); - - entry = entry.StartsWith("^") ? entry : $"^{entry}"; - - entry = entry.EndsWith("$") ? entry : $"{entry}$"; - - // Replace with the valid entry syntax - bypassList[i] = entry; + string[] bypassList = config.NoProxyList.Split( + new char[] { '|' }, + StringSplitOptions.RemoveEmptyEntries); + // Convert simplified syntax to standard regular expression syntax + string entry = null; + for (int i = 0; i < bypassList.Length; i++) + { + // Get the original entry + entry = bypassList[i].Trim(); + // . -> [.] because . means any char + entry = entry.Replace(".", "[.]"); + // * -> .* because * is a quantifier and need a char or group to apply to + entry = entry.Replace("*", ".*"); + + entry = entry.StartsWith("^") ? entry : $"^{entry}"; + + entry = entry.EndsWith("$") ? entry : $"{entry}$"; + + // Replace with the valid entry syntax + bypassList[i] = entry; + } + proxy.BypassList = bypassList; } - proxy.BypassList = bypassList; - } - HttpClientHandler httpHandlerWithProxy = (HttpClientHandler)httpHandler; - httpHandlerWithProxy.UseProxy = true; - httpHandlerWithProxy.Proxy = proxy; - return httpHandlerWithProxy; + httpHandler.Proxy = proxy; + } } return httpHandler; } @@ -384,7 +393,7 @@ protected override async Task SendAsync(HttpRequestMessage if (httpTimeout.Ticks == 0) childCts.Cancel(); else - childCts.CancelAfter(httpTimeout); + childCts.CancelAfter(httpTimeout); } response = await base.SendAsync(requestMessage, childCts == null ? cancellationToken : childCts.Token).ConfigureAwait(false); diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs index f129de25a..abe08554c 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs @@ -76,6 +76,7 @@ internal HttpClientConfig BuildHttpClientConfig() { return new HttpClientConfig( !insecureMode, + proxyProperties.useProxy, proxyProperties.proxyHost, proxyProperties.proxyPort, proxyProperties.proxyUser, diff --git a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs index 4266c585d..5f5ee7a28 100644 --- a/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs +++ b/Snowflake.Data/Core/Session/SFSessionHttpClientProxyProperties.cs @@ -6,6 +6,7 @@ namespace Snowflake.Data.Core internal class SFSessionHttpClientProxyProperties { + internal bool useProxy = false; internal string proxyHost = null; internal string proxyPort = null; internal string nonProxyHosts = null; @@ -22,7 +23,10 @@ internal class Extractor : IExtractor public SFSessionHttpClientProxyProperties ExtractProperties(SFSessionProperties propertiesDictionary) { var properties = new SFSessionHttpClientProxyProperties(); - if (Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY])) + + properties.useProxy = Boolean.Parse(propertiesDictionary[SFSessionProperty.USEPROXY]); + + if (properties.useProxy) { // Let's try to get the associated RestRequester propertiesDictionary.TryGetValue(SFSessionProperty.PROXYHOST, out properties.proxyHost); diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index 0621c5fb0..2c37269be 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,4 +1,4 @@ - + net6.0;net471;net472 net6.0 @@ -17,7 +17,7 @@ Full 7.3 - + @@ -31,7 +31,7 @@ - + @@ -45,7 +45,7 @@ - + full True @@ -55,13 +55,13 @@ full True - + true true $(Version) - +