Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brings updates from master to feature branch #2156

Merged
merged 23 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
88ddafd
fix(iot-device): Update connection string validation param
abhipsaMisra Jul 2, 2021
a355449
Update team codeowners
Jul 7, 2021
61f2c3f
fix(e2e-tests): Update resource generation script to generate resources
abhipsaMisra Jul 7, 2021
c76a64d
initial changeS (#2060)
bikamani Jul 8, 2021
c1b707c
Adding readme to explain authentication, retry and timeouts in Device…
vinagesh Jul 14, 2021
2d2f1eb
Last of error codes documentation (#2110) (#2074)
Jul 19, 2021
762db48
Rename readme per style and update reference (#2113)
Jul 19, 2021
84985f7
refactor(iot-service) Make all clients mockable (#2117)
vinagesh Jul 21, 2021
635da5f
refactor(iot-service): Move direct method request file out of JobClie…
abhipsaMisra Jul 22, 2021
e4bbb1e
refactor(prov-amqp, prov-mqtt, prov-http): Fix timeout tests failing …
timtay-microsoft Jul 23, 2021
4b5e014
fix(tools): Fix readme for ConsoleEventListener usage (#2123)
abhipsaMisra Jul 26, 2021
7ba7f3d
fix(tools): Fix readme for ConsoleEventListener usage
abhipsaMisra Jul 26, 2021
8ae3891
doc(all): Add documentation for the upcoming service TLS changes for …
timtay-microsoft Jul 28, 2021
0f0f9fb
fix(doc): Fix typo is readme
scb01 Aug 2, 2021
1e24ba7
Remove barustum from CODEOWNERS (#2126)
Aug 3, 2021
9ea8b6c
refactor(ado): Update area paths for analyzers pipeline (#2136)
timtay-microsoft Aug 6, 2021
2c3cb73
refactor(tools-logs): Update log capture script to be configurable an…
abhipsaMisra Aug 6, 2021
39baeb8
refactor(iot-device, prov-mqtt): Target new DotNetty version when not…
Aug 9, 2021
41e519b
refactor(doc): Add additional comments to log capture readme
abhipsaMisra Aug 10, 2021
5349c3f
Bump versions for 2021-08-12 release (#2143)
abhipsaMisra Aug 11, 2021
9782be8
fix(readme): Update the LTS support dates readme (#2147)
abhipsaMisra Aug 12, 2021
8f468d0
fix(e2e): get values from create call to avoid timing issues
Aug 24, 2021
bea49ae
Merge branch 'master' into abmisr/masterToPnpPreview
abhipsaMisra Aug 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Track1 .NET Azure IoT Hub and DPS SDKs

* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi
* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @jamdavi
4 changes: 4 additions & 0 deletions azureiot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{719D18A7-E943-461B-B777-0AAEC43916F5}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
configure_tls_protocol_version_and_ciphers.md = configure_tls_protocol_version_and_ciphers.md
device_connection_and_reliability_readme.md = device_connection_and_reliability_readme.md
readme.md = readme.md
supported_platforms.md = supported_platforms.md
test.runsettings = test.runsettings
EndProjectSection
EndProject
Expand Down
221 changes: 129 additions & 92 deletions common/src/service/ExceptionHandlingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
Expand All @@ -17,97 +18,88 @@ namespace Microsoft.Azure.Devices
{
internal class ExceptionHandlingHelper
{
public static IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> GetDefaultErrorMapping()
private static readonly IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> s_mappings =
new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>
{
var mappings = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>
{
{
HttpStatusCode.NoContent,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.NotFound,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.Conflict,
async (response) => new DeviceAlreadyExistsException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{ HttpStatusCode.BadRequest, async (response) => new ArgumentException(
message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) },

{
HttpStatusCode.Unauthorized,
async (response) => new UnauthorizedException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.Forbidden,
async (response) => new QuotaExceededException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.PreconditionFailed,
async (response) => new DeviceMessageLockLostException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.RequestEntityTooLarge,
async (response) => new MessageTooLargeException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.InternalServerError,
async (response) => new ServerErrorException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.ServiceUnavailable,
async (response) => new ServerBusyException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
(HttpStatusCode)429,
async (response) => new ThrottlingException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
}
};
HttpStatusCode.NoContent,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.NotFound,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Conflict,
async (response) => new DeviceAlreadyExistsException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.BadRequest, async (response) => new ArgumentException(
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Unauthorized,
async (response) => new UnauthorizedException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Forbidden,
async (response) => new QuotaExceededException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.PreconditionFailed,
async (response) => new DeviceMessageLockLostException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.RequestEntityTooLarge,
async (response) => new MessageTooLargeException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.InternalServerError,
async (response) => new ServerErrorException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.ServiceUnavailable,
async (response) => new ServerBusyException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
(HttpStatusCode)429,
async (response) => new ThrottlingException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
}
};

return mappings;
}
public static IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> GetDefaultErrorMapping() =>
s_mappings;

public static Task<string> GetExceptionMessageAsync(HttpResponseMessage response)
{
return response.Content.ReadAsStringAsync();
}

/// <summary>
/// Get the fully qualified error code from the http response message, if exists
/// Get the fully-qualified error code from the HTTP response message, if exists.
/// </summary>
/// <param name="response">The http response message</param>
/// <returns>The fully qualified error code, or the response status code if no error code was provided.</returns>
/// <param name="response">The HTTP response message</param>
/// <returns>The fully-qualified error code, or the response status code, if no error code was provided.</returns>
public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage response)
{
// First we will attempt to retrieve the error code from the response content.
Expand All @@ -121,22 +113,67 @@ public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage re
// to 'error code' enum mapping, the SDK will check if both values are a match. If so, the SDK will populate the exception with the proper Code. In the case where
// there is a mismatch between the error code and the description, the SDK returns ErrorCode.InvalidErrorCode and log a warning.

int errorCode;
int errorCodeValue = (int)ErrorCode.InvalidErrorCode;
try
{
IoTHubExceptionResult responseContent = JsonConvert
.DeserializeObject<IoTHubExceptionResult>(responseContentStr);
Dictionary<string, string> messageFields = JsonConvert
.DeserializeObject<Dictionary<string, string>>(responseContent.Message);
IoTHubExceptionResult responseContent = JsonConvert.DeserializeObject<IoTHubExceptionResult>(responseContentStr);

if (messageFields != null
&& messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj))
try
{
errorCode = Convert.ToInt32(errorCodeObj, CultureInfo.InvariantCulture);
Dictionary<string, string> messageFields = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent.Message);

if (messageFields != null
&& messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj))
{
// The result of TryParse is not being tracked since errorCodeValue has already been initialized to a default value of InvalidErrorCode.
_ = int.TryParse(errorCodeObj, NumberStyles.Any, CultureInfo.InvariantCulture, out errorCodeValue);
}
}
else
catch (JsonReaderException ex)
{
return ErrorCode.InvalidErrorCode;
if (Logging.IsEnabled)
Logging.Error(null, $"Failed to deserialize error message into a dictionary: {ex}. Message body: '{responseContentStr}.'");

// In some scenarios, the error response string is a ';' delimited string with the service-returned error code.
const char errorFieldsDelimiter = ';';
string[] messageFields = responseContent.Message?.Split(errorFieldsDelimiter);

if (messageFields != null)
{
foreach (string messageField in messageFields)
{
#if NET451 || NET472 || NETSTANDARD2_0
if (messageField.IndexOf(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase) >= 0)
#else
if (messageField.Contains(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase))
#endif
{
const char errorCodeDelimiter = ':';

#if NET451 || NET472 || NETSTANDARD2_0
if (messageField.IndexOf(errorCodeDelimiter) >= 0)
#else
if (messageField.Contains(errorCodeDelimiter))
#endif
{
string[] errorCodeFields = messageField.Split(errorCodeDelimiter);
if (Enum.TryParse(errorCodeFields[1], out ErrorCode errorCode))
{
errorCodeValue = (int)errorCode;
}
}
}
break;
}
}
else
{
if (Logging.IsEnabled)
Logging.Error(null, $"Failed to deserialize error message into a dictionary and could not parse ';' delimited string either: {ex}." +
$" Message body: '{responseContentStr}.'");

return ErrorCode.InvalidErrorCode;
}
}
}
catch (JsonReaderException ex)
Expand All @@ -152,15 +189,15 @@ public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage re
if (headerErrorCodeString != null
&& Enum.TryParse(headerErrorCodeString, out ErrorCode headerErrorCode))
{
if ((int)headerErrorCode == errorCode)
if ((int)headerErrorCode == errorCodeValue)
{
// We have a match. Therefore, return the proper error code.
return headerErrorCode;
}

if (Logging.IsEnabled)
Logging.Error(null, $"There is a mismatch between the error code retrieved from the response content and the response header." +
$"Content error code: {errorCode}. Header error code description: {(int)headerErrorCode}.");
$"Content error code: {errorCodeValue}. Header error code description: {(int)headerErrorCode}.");
}

return ErrorCode.InvalidErrorCode;
Expand Down
5 changes: 2 additions & 3 deletions common/src/service/HttpClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ internal sealed class HttpClientHelper : IHttpClientHelper
public HttpClientHelper(
Uri baseAddress,
IAuthorizationHeaderProvider authenticationHeaderProvider,
IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> defaultErrorMapping,
IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> defaultErrorMapping,
TimeSpan timeout,
IWebProxy customHttpProxy,
int connectionLeaseTimeoutMilliseconds)
{
_baseAddress = baseAddress;
_authenticationHeaderProvider = authenticationHeaderProvider;
_defaultErrorMapping = new ReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>(defaultErrorMapping);
_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
Expand Down Expand Up @@ -924,7 +924,6 @@ internal static HttpMessageHandler CreateDefaultHttpMessageHandler(IWebProxy web
#endif
#pragma warning restore CA2000 // Dispose objects before losing scope


if (webProxy != DefaultWebProxySettings.Instance)
{
httpMessageHandler.UseProxy = webProxy != null;
Expand Down
Loading