common/src/service/HttpClientHelper.cs (807 lines of code) (raw):

// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Amqp.Transport; using Microsoft.Azure.Devices.Common; using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.Azure.Devices.Common.Extensions; using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; using static Microsoft.Azure.Devices.Shared.HttpMessageHelper; namespace Microsoft.Azure.Devices { internal sealed class HttpClientHelper : IHttpClientHelper { private const string ApplicationJson = "application/json"; private readonly Uri _baseAddress; private readonly IAuthorizationHeaderProvider _authenticationHeaderProvider; private readonly IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> _defaultErrorMapping; private readonly TimeSpan _defaultOperationTimeout; // IDisposables private readonly HttpClient _httpClientWithDefaultTimeout; private readonly HttpClient _httpClientWithNoTimeout; public HttpClientHelper( Uri baseAddress, IAuthorizationHeaderProvider authenticationHeaderProvider, IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> defaultErrorMapping, TimeSpan timeout, IWebProxy customHttpProxy, int connectionLeaseTimeoutMilliseconds) { _baseAddress = baseAddress; _authenticationHeaderProvider = authenticationHeaderProvider; _defaultErrorMapping = defaultErrorMapping; _defaultOperationTimeout = timeout; // We need two types of HttpClients, one with our default operation timeout, and one without. The one without will rely on // a cancellation token. _httpClientWithDefaultTimeout = CreateDefaultClient(customHttpProxy, _baseAddress, connectionLeaseTimeoutMilliseconds); _httpClientWithDefaultTimeout.Timeout = _defaultOperationTimeout; _httpClientWithDefaultTimeout.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(CommonConstants.MediaTypeForDeviceManagementApis)); _httpClientWithDefaultTimeout.DefaultRequestHeaders.ExpectContinue = false; _httpClientWithNoTimeout = CreateDefaultClient(customHttpProxy, _baseAddress, connectionLeaseTimeoutMilliseconds); _httpClientWithNoTimeout.Timeout = Timeout.InfiniteTimeSpan; _httpClientWithNoTimeout.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(CommonConstants.MediaTypeForDeviceManagementApis)); _httpClientWithNoTimeout.DefaultRequestHeaders.ExpectContinue = false; TlsVersions.Instance.SetLegacyAcceptableVersions(); } public Task<T> GetAsync<T>( Uri requestUri, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) { return GetAsync<T>(requestUri, _defaultOperationTimeout, errorMappingOverrides, customHeaders, true, cancellationToken); } public Task<T> GetAsync<T>( Uri requestUri, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, bool throwIfNotFound, CancellationToken cancellationToken) { return GetAsync<T>(requestUri, _defaultOperationTimeout, errorMappingOverrides, customHeaders, throwIfNotFound, cancellationToken); } public async Task<T> GetAsync<T>( Uri requestUri, TimeSpan operationTimeout, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, bool throwIfNotFound, CancellationToken cancellationToken) { T result = default; if (operationTimeout != _defaultOperationTimeout && operationTimeout > TimeSpan.Zero) { if (throwIfNotFound) { await ExecuteWithCustomOperationTimeoutAsync( HttpMethod.Get, new Uri(_baseAddress, requestUri), operationTimeout, (requestMsg, token) => AddCustomHeaders(requestMsg, customHeaders), IsMappedToException, async (message, token) => result = await ReadResponseMessageAsync<T>(message, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); } else { await ExecuteWithCustomOperationTimeoutAsync( HttpMethod.Get, new Uri(_baseAddress, requestUri), operationTimeout, (requestMsg, token) => AddCustomHeaders(requestMsg, customHeaders), message => !(message.IsSuccessStatusCode || message.StatusCode == HttpStatusCode.NotFound), async (message, token) => result = message.StatusCode == HttpStatusCode.NotFound ? default : await ReadResponseMessageAsync<T>(message, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); } } else { if (throwIfNotFound) { await ExecuteAsync( HttpMethod.Get, new Uri(_baseAddress, requestUri), (requestMsg, token) => AddCustomHeaders(requestMsg, customHeaders), async (message, token) => result = await ReadResponseMessageAsync<T>(message, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); } else { await ExecuteAsync( _httpClientWithDefaultTimeout, HttpMethod.Get, new Uri(_baseAddress, requestUri), (requestMsg, token) => AddCustomHeaders(requestMsg, customHeaders), message => !(message.IsSuccessStatusCode || message.StatusCode == HttpStatusCode.NotFound), async (message, token) => result = message.StatusCode == HttpStatusCode.NotFound ? default : await ReadResponseMessageAsync<T>(message, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); } } return result; } public async Task<T> PutAsync<T>( Uri requestUri, T entity, PutOperationType operationType, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) where T : IETagHolder { T result = default; await ExecuteAsync( HttpMethod.Put, new Uri(_baseAddress, requestUri), (requestMsg, token) => { InsertEtag(requestMsg, entity, operationType); SetHttpRequestMessageContent(requestMsg, entity); return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync<T>(httpClient, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); return result; } public async Task<T2> PutAsync<T, T2>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { T2 result = default; await ExecuteAsync( HttpMethod.Put, new Uri(_baseAddress, requestUri), (requestMsg, token) => { SetHttpRequestMessageContent<T>(requestMsg, entity); return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken).ConfigureAwait(false); return result; } public async Task PutAsync<T>( Uri requestUri, T entity, string etag, PutOperationType operationType, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { await ExecuteAsync( HttpMethod.Put, new Uri(_baseAddress, requestUri), (requestMsg, token) => { InsertEtag(requestMsg, etag, operationType); SetHttpRequestMessageContent<T>(requestMsg, entity); return Task.FromResult(0); }, null, errorMappingOverrides, cancellationToken).ConfigureAwait(false); } public async Task<T2> PutAsync<T, T2>( Uri requestUri, T entity, string etag, PutOperationType operationType, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { T2 result = default; await ExecuteAsync( HttpMethod.Put, new Uri(_baseAddress, requestUri), (requestMsg, token) => { // TODO: skintali: Use string etag when service side changes are ready InsertEtag(requestMsg, etag, operationType); SetHttpRequestMessageContent<T>(requestMsg, entity); return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken).ConfigureAwait(false); return result; } public async Task PatchAsync<T>(Uri requestUri, T entity, string etag, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { await ExecuteAsync( new HttpMethod("PATCH"), new Uri(_baseAddress, requestUri), (requestMsg, token) => { InsertEtag(requestMsg, etag, PutOperationType.UpdateEntity); SetHttpRequestMessageContent<T>(requestMsg, entity); return Task.FromResult(0); }, null, errorMappingOverrides, cancellationToken).ConfigureAwait(false); } public async Task<T2> PatchAsync<T, T2>(Uri requestUri, T entity, string etag, PutOperationType putOperationType, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { T2 result = default; await ExecuteAsync( new HttpMethod("PATCH"), new Uri(_baseAddress, requestUri), (requestMsg, token) => { InsertEtag(requestMsg, etag, putOperationType); SetHttpRequestMessageContent<T>(requestMsg, entity); return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync<T2>(httpClient, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken).ConfigureAwait(false); return result; } private static async Task<T> ReadResponseMessageAsync<T>(HttpResponseMessage message, CancellationToken token) { if (typeof(T) == typeof(HttpResponseMessage)) { return (T)(object)message; } string str = await message.Content.ReadHttpContentAsStringAsync(token).ConfigureAwait(false); T entity = JsonConvert.DeserializeObject<T>(str, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); // Etag in the header is considered authoritative var eTagHolder = entity as IETagHolder; if (eTagHolder != null) { if (message.Headers.ETag != null && !string.IsNullOrWhiteSpace(message.Headers.ETag.Tag)) { // RDBug 3429280:Make the version field of Device object internal eTagHolder.ETag = message.Headers.ETag.Tag; } } return entity; } private static Task AddCustomHeaders(HttpRequestMessage requestMessage, IDictionary<string, string> customHeaders) { if (customHeaders != null) { foreach (KeyValuePair<string, string> header in customHeaders) { requestMessage.Headers.Add(header.Key, header.Value); } } return Task.FromResult(0); } private static void InsertEtag(HttpRequestMessage requestMessage, IETagHolder entity, PutOperationType operationType) { if (operationType == PutOperationType.CreateEntity) { return; } if (operationType == PutOperationType.ForceUpdateEntity) { const string etag = "\"*\""; requestMessage.Headers.IfMatch.Add(new EntityTagHeaderValue(etag)); } else { InsertEtag(requestMessage, entity.ETag); } } private static void InsertEtag(HttpRequestMessage requestMessage, string etag, PutOperationType operationType) { if (operationType == PutOperationType.CreateEntity) { return; } string etagString = "\"*\""; if (operationType == PutOperationType.ForceUpdateEntity) { requestMessage.Headers.IfMatch.Add(new EntityTagHeaderValue(etagString)); } else { InsertEtag(requestMessage, etag); } } private static void InsertEtag(HttpRequestMessage requestMessage, string etag) { if (string.IsNullOrWhiteSpace(etag)) { throw new ArgumentException("The entity does not have its ETag set."); } if (!etag.StartsWith("\"", StringComparison.OrdinalIgnoreCase)) { etag = "\"" + etag; } if (!etag.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) { etag += "\""; } requestMessage.Headers.IfMatch.Add(new EntityTagHeaderValue(etag)); } private IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> MergeErrorMapping( IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides) { var mergedMapping = _defaultErrorMapping.ToDictionary(mapping => mapping.Key, mapping => mapping.Value); if (errorMappingOverrides != null) { foreach (KeyValuePair<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> @override in errorMappingOverrides) { mergedMapping[@override.Key] = @override.Value; } } return mergedMapping; } public Task PostAsync<T>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) { return PostAsyncHelper( requestUri, entity, TimeSpan.Zero, errorMappingOverrides, customHeaders, null, null, ReadResponseMessageAsync<HttpResponseMessage>, cancellationToken); } public Task PostAsync<T>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, TimeSpan operationTimeout, CancellationToken cancellationToken) { return PostAsyncHelper( requestUri, entity, operationTimeout, errorMappingOverrides, customHeaders, null, null, ReadResponseMessageAsync<HttpResponseMessage>, cancellationToken); } public async Task<T2> PostAsync<T1, T2>( Uri requestUri, T1 entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) { T2 result = default; await PostAsyncHelper( requestUri, entity, TimeSpan.Zero, errorMappingOverrides, customHeaders, null, null, async (message, token) => result = await ReadResponseMessageAsync<T2>(message, token).ConfigureAwait(false), cancellationToken) .ConfigureAwait(false); return result; } public async Task<T2> PostAsync<T, T2>( Uri requestUri, T entity, TimeSpan operationTimeout, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) { T2 result = default; await PostAsyncHelper( requestUri, entity, operationTimeout, errorMappingOverrides, customHeaders, null, null, async (message, token) => result = await ReadResponseMessageAsync<T2>(message, token).ConfigureAwait(false), cancellationToken) .ConfigureAwait(false); return result; } public async Task<T2> PostAsync<T, T2>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, MediaTypeHeaderValue customContentType, ICollection<string> customContentEncoding, CancellationToken cancellationToken) { T2 result = default; await PostAsyncHelper( requestUri, entity, TimeSpan.Zero, errorMappingOverrides, customHeaders, customContentType, customContentEncoding, async (message, token) => result = await ReadResponseMessageAsync<T2>(message, token).ConfigureAwait(false), cancellationToken) .ConfigureAwait(false); return result; } public async Task<HttpResponseMessage> PostAsync<T>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, MediaTypeHeaderValue customContentType, ICollection<string> customContentEncoding, CancellationToken cancellationToken) { HttpResponseMessage result = default; await PostAsyncHelper( requestUri, entity, TimeSpan.Zero, errorMappingOverrides, customHeaders, customContentType, customContentEncoding, (message, token) => { result = message; return TaskHelpers.CompletedTask; }, cancellationToken) .ConfigureAwait(false); return result; } private Task PostAsyncHelper<T1>( Uri requestUri, T1 entity, TimeSpan operationTimeout, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, MediaTypeHeaderValue customContentType, ICollection<string> customContentEncoding, Func<HttpResponseMessage, CancellationToken, Task> processResponseMessageAsync, CancellationToken cancellationToken) { Func<HttpRequestMessage, CancellationToken, Task> modifyRequestMessageFunc = (requestMsg, token) => { AddCustomHeaders(requestMsg, customHeaders); if (entity != null) { if (typeof(T1) == typeof(byte[])) { requestMsg.Content = new ByteArrayContent((byte[])(object)entity); } else if (typeof(T1) == typeof(string)) { // only used to send batched messages on Http runtime requestMsg.Content = new StringContent((string)(object)entity); requestMsg.Content.Headers.ContentType = new MediaTypeHeaderValue(CommonConstants.BatchedMessageContentType); } else { string str = JsonConvert.SerializeObject(entity, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson); } } if (customContentType != null) { requestMsg.Content.Headers.ContentType = customContentType; } if (customContentEncoding != null && customContentEncoding.Count > 0) { foreach (string contentEncoding in customContentEncoding) { requestMsg.Content.Headers.ContentEncoding.Add(contentEncoding); } } return Task.FromResult(0); }; if (operationTimeout != _defaultOperationTimeout && operationTimeout > TimeSpan.Zero) { return ExecuteWithCustomOperationTimeoutAsync( HttpMethod.Post, new Uri(_baseAddress, requestUri), operationTimeout, modifyRequestMessageFunc, IsMappedToException, processResponseMessageAsync, errorMappingOverrides, cancellationToken); } else { return ExecuteAsync( HttpMethod.Post, new Uri(_baseAddress, requestUri), modifyRequestMessageFunc, processResponseMessageAsync, errorMappingOverrides, cancellationToken); } } public Task DeleteAsync<T>( Uri requestUri, T entity, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) where T : IETagHolder { return ExecuteAsync( HttpMethod.Delete, new Uri(_baseAddress, requestUri), (requestMsg, token) => { InsertEtag(requestMsg, entity.ETag); AddCustomHeaders(requestMsg, customHeaders); return TaskHelpers.CompletedTask; }, null, errorMappingOverrides, cancellationToken); } public async Task<T> DeleteAsync<T>( Uri requestUri, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, IDictionary<string, string> customHeaders, CancellationToken cancellationToken) { T result = default; await ExecuteAsync( HttpMethod.Delete, new Uri(_baseAddress, requestUri), (requestMsg, token) => { AddCustomHeaders(requestMsg, customHeaders); return TaskHelpers.CompletedTask; }, async (message, token) => result = await ReadResponseMessageAsync<T>(message, token).ConfigureAwait(false), errorMappingOverrides, cancellationToken) .ConfigureAwait(false); return result; } private async Task ExecuteAsync( HttpMethod httpMethod, Uri requestUri, Func<HttpRequestMessage, CancellationToken, Task> modifyRequestMessageAsync, Func<HttpResponseMessage, CancellationToken, Task> processResponseMessageAsync, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { await ExecuteAsync( _httpClientWithDefaultTimeout, httpMethod, requestUri, modifyRequestMessageAsync, IsMappedToException, processResponseMessageAsync, errorMappingOverrides, cancellationToken) .ConfigureAwait(false); } private async Task ExecuteWithCustomOperationTimeoutAsync( HttpMethod httpMethod, Uri requestUri, TimeSpan operationTimeout, Func<HttpRequestMessage, CancellationToken, Task> modifyRequestMessageAsync, Func<HttpResponseMessage, bool> isMappedToException, Func<HttpResponseMessage, CancellationToken, Task> processResponseMessageAsync, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using var cts = new CancellationTokenSource(operationTimeout); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token); await ExecuteAsync( _httpClientWithNoTimeout, httpMethod, requestUri, modifyRequestMessageAsync, isMappedToException, processResponseMessageAsync, errorMappingOverrides, linkedCts.Token) .ConfigureAwait(false); } public static bool IsMappedToException(HttpResponseMessage message) { bool isMappedToException = !message.IsSuccessStatusCode; // Get any IotHubErrorCode information from the header for special case exemption of exception throwing string iotHubErrorCodeAsString = message.Headers.GetFirstValueOrNull(CommonConstants.IotHubErrorCode); if (Enum.TryParse(iotHubErrorCodeAsString, out ErrorCode iotHubErrorCode)) { switch (iotHubErrorCode) { case ErrorCode.BulkRegistryOperationFailure: isMappedToException = false; break; } } return isMappedToException; } public void Dispose() { _httpClientWithDefaultTimeout.Dispose(); _httpClientWithNoTimeout.Dispose(); } private async Task ExecuteAsync( HttpClient httpClient, HttpMethod httpMethod, Uri requestUri, Func<HttpRequestMessage, CancellationToken, Task> modifyRequestMessageAsync, Func<HttpResponseMessage, bool> isMappedToException, Func<HttpResponseMessage, CancellationToken, Task> processResponseMessageAsync, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMappingOverrides, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, httpMethod.Method, requestUri, nameof(ExecuteAsync)); try { IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> mergedErrorMapping = MergeErrorMapping(errorMappingOverrides); using var msg = new HttpRequestMessage(httpMethod, requestUri); msg.Headers.Add(HttpRequestHeader.Authorization.ToString(), _authenticationHeaderProvider.GetAuthorizationHeader()); msg.Headers.Add(HttpRequestHeader.UserAgent.ToString(), Utils.GetClientVersion()); if (modifyRequestMessageAsync != null) { await modifyRequestMessageAsync(msg, cancellationToken).ConfigureAwait(false); } // TODO: pradeepc - find out the list of exceptions that HttpClient can throw. HttpResponseMessage responseMsg; try { responseMsg = await httpClient.SendAsync(msg, cancellationToken).ConfigureAwait(false); if (responseMsg == null) { throw new InvalidOperationException("The response message was null when executing operation {0}.".FormatInvariant(httpMethod)); } if (!isMappedToException(responseMsg)) { if (processResponseMessageAsync != null) { await processResponseMessageAsync(responseMsg, cancellationToken).ConfigureAwait(false); } } } catch (AggregateException ex) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); ReadOnlyCollection<Exception> innerExceptions = ex.Flatten().InnerExceptions; if (innerExceptions.Any(Fx.IsFatal)) { throw; } // Apparently HttpClient throws AggregateException when a timeout occurs. // TODO: pradeepc - need to confirm this with ASP.NET team if (innerExceptions.Any(e => e is TimeoutException)) { throw new IotHubCommunicationException(ex.Message, ex); } throw new IotHubException(ex.Message, ex); } catch (TimeoutException ex) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); throw new IotHubCommunicationException(ex.Message, ex); } catch (IOException ex) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); throw new IotHubCommunicationException(ex.Message, ex); } catch (HttpRequestException ex) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); throw new IotHubCommunicationException(ex.Message, ex); } catch (TaskCanceledException ex) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); // Unfortunately TaskCanceledException is thrown when HttpClient times out. if (cancellationToken.IsCancellationRequested) { throw new IotHubException(ex.Message, ex); } throw new IotHubCommunicationException(string.Format(CultureInfo.InvariantCulture, "The {0} operation timed out.", httpMethod), ex); } catch (Exception ex) when (!Fx.IsFatal(ex)) { if (Logging.IsEnabled) Logging.Error(this, ex, nameof(ExecuteAsync)); throw new IotHubException(ex.Message, ex); } if (isMappedToException(responseMsg)) { Exception mappedEx = await MapToExceptionAsync(responseMsg, mergedErrorMapping).ConfigureAwait(false); throw mappedEx; } } finally { if (Logging.IsEnabled) Logging.Exit(this, httpMethod.Method, requestUri, nameof(ExecuteAsync)); } } private static async Task<Exception> MapToExceptionAsync( HttpResponseMessage response, IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> errorMapping) { if (!errorMapping.TryGetValue(response.StatusCode, out Func<HttpResponseMessage, Task<Exception>> func)) { return new IotHubException( await ExceptionHandlingHelper.GetExceptionMessageAsync(response).ConfigureAwait(false), isTransient: true); } Func<HttpResponseMessage, Task<Exception>> mapToExceptionFunc = errorMapping[response.StatusCode]; Task<Exception> exception = mapToExceptionFunc(response); return await exception.ConfigureAwait(false); } internal static HttpClient CreateDefaultClient(IWebProxy webProxy, Uri baseUri, int connectionLeaseTimeoutMilliseconds) { // This http client will dispose of this httpMessageHandler when the http client is disposed, so no need to save a local copy #pragma warning disable CA2000 // Dispose objects before losing scope (httpClient and messageHandler are disposed outside of this scope) return new HttpClient(CreateDefaultHttpMessageHandler(webProxy, baseUri, connectionLeaseTimeoutMilliseconds)) #pragma warning restore CA2000 // Dispose objects before losing scope { // Timeouts are handled by the pipeline Timeout = Timeout.InfiniteTimeSpan, BaseAddress = baseUri }; } internal static HttpMessageHandler CreateDefaultHttpMessageHandler(IWebProxy webProxy, Uri baseUri, int connectionLeaseTimeoutMilliseconds) { HttpMessageHandler httpMessageHandler = null; #if NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER var socketsHandler = new SocketsHttpHandler(); socketsHandler.SslOptions.EnabledSslProtocols = TlsVersions.Instance.Preferred; if (!TlsVersions.Instance.CertificateRevocationCheck) { socketsHandler.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; } if (webProxy != DefaultWebProxySettings.Instance) { socketsHandler.UseProxy = webProxy != null; socketsHandler.Proxy = webProxy; } httpMessageHandler = socketsHandler; #else var httpClientHandler = new HttpClientHandler(); #if !NET451 httpClientHandler.SslProtocols = TlsVersions.Instance.Preferred; httpClientHandler.CheckCertificateRevocationList = TlsVersions.Instance.CertificateRevocationCheck; #endif if (webProxy != DefaultWebProxySettings.Instance) { httpClientHandler.UseProxy = webProxy != null; httpClientHandler.Proxy = webProxy; } httpMessageHandler = httpClientHandler; #endif ServicePointHelpers.SetLimits(httpMessageHandler, baseUri, connectionLeaseTimeoutMilliseconds); return httpMessageHandler; } } }