From 7e89a82d5e5e6bf2744001a2a5c185994e891232 Mon Sep 17 00:00:00 2001 From: Matt M Date: Tue, 22 Jan 2019 11:08:32 -0700 Subject: [PATCH 1/2] -- Provide a way for options on HttpClient and HttpWebRequest to be set. -- Add property to AvaTaxClientOptions to allow for the request timeout to be set. --- src/AvaTaxClient.cs | 66 +++++++++++++++++++++++++++------ src/AvaTaxClientOptions.cs | 21 +++++++++++ src/Avalara.AvaTax.net20.csproj | 1 + src/Avalara.AvaTax.net45.csproj | 1 + 4 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 src/AvaTaxClientOptions.cs diff --git a/src/AvaTaxClient.cs b/src/AvaTaxClient.cs index 7abd8c55..f6637342 100644 --- a/src/AvaTaxClient.cs +++ b/src/AvaTaxClient.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using System.Threading; #endif using System.Net; using System.Text; @@ -21,6 +22,7 @@ namespace Avalara.AvaTax.RestClient /// public partial class AvaTaxClient { + private AvaTaxClientOptions _options = new AvaTaxClientOptions(); private Dictionary _clientHeaders = new Dictionary(); private Uri _envUri; #if PORTABLE @@ -79,9 +81,9 @@ public AvaTaxClient(string appName, string appVersion, string machineName, Uri c WithClientIdentifier(appName, appVersion, machineName); _envUri = customEnvironment; } -#endregion + #endregion -#region Security + #region Security /// /// Sets the default security header string /// @@ -163,9 +165,20 @@ public AvaTaxClient WithClientIdentifier(string appName, string appVersion, stri _clientHeaders.Add(Constants.AVALARA_CLIENT_HEADER, String.Format("{0}; {1}; {2}; {3}; {4}", appName, appVersion, "CSharpRestClient", API_VERSION, machineName)); return this; } -#endregion + #endregion + + /// + /// Sets options for the AvaTaxClient and how it behaves. + /// + /// + /// + public AvaTaxClient WithOptions(AvaTaxClientOptions options) + { + _options = options ?? new AvaTaxClientOptions(); + return this; + } -#region REST Call Interface + #region REST Call Interface #if PORTABLE /// /// Implementation of asynchronous client APIs @@ -233,9 +246,9 @@ public FileResult RestCallFile(string verb, AvaTaxPath relativePath, object payl } } #endif -#endregion + #endregion -#region Implementation + #region Implementation private JsonSerializerSettings _serializer_settings = null; private JsonSerializerSettings SerializerSettings { @@ -320,7 +333,7 @@ private async Task InternalRestCallAsync(CallDuration cd, s using (var request = new HttpRequestMessage()) { request.Method = new HttpMethod(verb); request.RequestUri = new Uri(_envUri, relativePath.ToString()); - + // Add headers foreach (var key in _clientHeaders.Keys) { @@ -334,7 +347,28 @@ private async Task InternalRestCallAsync(CallDuration cd, s // Send cd.FinishSetup(); - return await _client.SendAsync(request).ConfigureAwait(false); + + CancellationTokenSource timeoutTokenSource = null; + + try + { + CancellationToken token = default(CancellationToken); + if (_options != null && _options.Timeout.HasValue) + { + timeoutTokenSource = new CancellationTokenSource(_options.Timeout.Value); + token = timeoutTokenSource.Token; + } + + return await _client.SendAsync(request, token).ConfigureAwait(false); + } + finally + { + if (timeoutTokenSource != null) + { + timeoutTokenSource.Dispose(); + timeoutTokenSource = null; + } + } } } @@ -452,6 +486,11 @@ private FileResult RestCallFile(string verb, AvaTaxPath relativePath, object con var wr = (HttpWebRequest)WebRequest.Create(path); wr.Proxy = null; + if (_options != null && _options.Timeout.HasValue) + { + wr.Timeout = Convert.ToInt32(Math.Floor(_options.Timeout.Value.TotalMilliseconds)); + } + // Add headers foreach (var key in _clientHeaders.Keys) { @@ -483,7 +522,7 @@ private FileResult RestCallFile(string verb, AvaTaxPath relativePath, object con using (var inStream = response.GetResponseStream()) { const int BUFFER_SIZE = 1024; var chunks = new List(); - var totalBytes = 0; + var totalBytes = 0; var bytesRead = 0; do @@ -499,7 +538,7 @@ private FileResult RestCallFile(string verb, AvaTaxPath relativePath, object con } totalBytes += bytesRead; } while (bytesRead > 0); - + if(totalBytes <= 0) { throw new IOException("Response contained no data"); } @@ -559,6 +598,11 @@ private string RestCallString(string verb, AvaTaxPath relativePath, object conte var wr = (HttpWebRequest)WebRequest.Create(path); wr.Proxy = null; + if (_options != null && _options.Timeout.HasValue) + { + wr.Timeout = Convert.ToInt32(Math.Floor(_options.Timeout.Value.TotalMilliseconds)); + } + // Add headers foreach (var key in _clientHeaders.Keys) { @@ -601,7 +645,7 @@ private string RestCallString(string verb, AvaTaxPath relativePath, object conte } } - // Catch a web exception + // Catch a web exception } catch (WebException webex) { HttpWebResponse httpWebResponse = webex.Response as HttpWebResponse; if (httpWebResponse != null) { diff --git a/src/AvaTaxClientOptions.cs b/src/AvaTaxClientOptions.cs new file mode 100644 index 00000000..a0f8da24 --- /dev/null +++ b/src/AvaTaxClientOptions.cs @@ -0,0 +1,21 @@ +using System; +#if PORTABLE +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using System.Threading; +#endif + +namespace Avalara.AvaTax.RestClient +{ + /// + /// Configuration options for the + /// + public class AvaTaxClientOptions + { + /// + /// The request timeout. + /// + public TimeSpan? Timeout { get; set; } + } +} diff --git a/src/Avalara.AvaTax.net20.csproj b/src/Avalara.AvaTax.net20.csproj index 7573d4af..f8916b44 100644 --- a/src/Avalara.AvaTax.net20.csproj +++ b/src/Avalara.AvaTax.net20.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Avalara.AvaTax.net45.csproj b/src/Avalara.AvaTax.net45.csproj index 8a1fd719..e83e60ca 100644 --- a/src/Avalara.AvaTax.net45.csproj +++ b/src/Avalara.AvaTax.net45.csproj @@ -49,6 +49,7 @@ + From f33c53ad9e096551597bc0cee4b9a5a85e9a1f1e Mon Sep 17 00:00:00 2001 From: Matt M Date: Tue, 30 Jun 2020 12:58:08 -0600 Subject: [PATCH 2/2] Added comments for request timeout. --- src/AvaTaxClient.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/AvaTaxClient.cs b/src/AvaTaxClient.cs index 83d9e40b..c657aec0 100644 --- a/src/AvaTaxClient.cs +++ b/src/AvaTaxClient.cs @@ -28,7 +28,11 @@ public partial class AvaTaxClient private Dictionary _clientHeaders = new Dictionary(); private Uri _envUri; #if PORTABLE - private static HttpClient _client = new HttpClient() { Timeout = TimeSpan.FromMinutes(20) }; + private static HttpClient _client = new HttpClient() + { + // This timeout will be the default timeout used for all requests if if a timeout is not provided with the AvaTaxClientOptions + Timeout = TimeSpan.FromMinutes(20) + }; #endif /// @@ -515,8 +519,9 @@ private FileResult RestCallFile(string verb, AvaTaxPath relativePath, object con // Use HttpWebRequest so we can get a decent response var wr = (HttpWebRequest)WebRequest.Create(path); wr.Proxy = null; - wr.Timeout = 1200000; + // Default to 20 minutes if a timeout has not been provided in the AvaTaxClientOptions. + wr.Timeout = 1200000; if (_options != null && _options.Timeout.HasValue) { wr.Timeout = Convert.ToInt32(Math.Floor(_options.Timeout.Value.TotalMilliseconds));