Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Remove all LINQ usage from product code #260

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [Unreleased]

## [1.4.3] - 2024-05-24

### Changed

- Remove all LINQ usage from product code

## [1.4.2] - 2024-05-21

### Added
Expand Down
116 changes: 79 additions & 37 deletions src/HttpClientRequestAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
Expand Down Expand Up @@ -388,7 +387,9 @@ private async Task ThrowIfFailedResponse(HttpResponseMessage response, Dictionar

var statusCodeAsInt = (int)response.StatusCode;
var statusCodeAsString = statusCodeAsInt.ToString();
var responseHeadersDictionary = response.Headers.ToDictionary(x => x.Key, y => y.Value, StringComparer.OrdinalIgnoreCase);
var responseHeadersDictionary = new Dictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var header in response.Headers)
responseHeadersDictionary[header.Key] = header.Value;
ParsableFactory<IParsable>? errorFactory;
if(errorMapping == null ||
!errorMapping.TryGetValue(statusCodeAsString, out errorFactory) &&
Expand Down Expand Up @@ -470,51 +471,86 @@ private async Task<HttpResponseMessage> GetHttpResponseMessage(RequestInformatio
var ex = new InvalidOperationException("Could not get a response after calling the service");
throw ex;
}
if(response.Headers.TryGetValues("Content-Length", out var contentLengthValues) &&
contentLengthValues.Any() &&
contentLengthValues.First() is string firstContentLengthValue &&
int.TryParse(firstContentLengthValue, out var contentLength))
if(response.Headers.TryGetValues("Content-Length", out var contentLengthValues))
{
activityForAttributes?.SetTag("http.response_content_length", contentLength);
using var contentLengthEnumerator = contentLengthValues.GetEnumerator();
if(contentLengthEnumerator.MoveNext() && int.TryParse(contentLengthEnumerator.Current, out var contentLength))
{
activityForAttributes?.SetTag("http.response_content_length", contentLength);
}
}
if(response.Headers.TryGetValues("Content-Type", out var contentTypeValues) &&
contentTypeValues.Any() &&
contentTypeValues.First() is string firstContentTypeValue)
if(response.Headers.TryGetValues("Content-Type", out var contentTypeValues))
{
activityForAttributes?.SetTag("http.response_content_type", firstContentTypeValue);
using var contentTypeEnumerator = contentTypeValues.GetEnumerator();
if(contentTypeEnumerator.MoveNext())
{
activityForAttributes?.SetTag("http.response_content_type", contentTypeEnumerator.Current);
}
}
activityForAttributes?.SetTag("http.status_code", (int)response.StatusCode);
activityForAttributes?.SetTag("http.flavor", $"{response.Version.Major}.{response.Version.Minor}");

return await RetryCAEResponseIfRequired(response, requestInfo, cancellationToken, claims, activityForAttributes).ConfigureAwait(false);
}

private static readonly Regex caeValueRegex = new("\"([^\"]*)\"", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));

/// <summary>
/// The key for the event raised by tracing when an authentication challenge is received
/// </summary>
public const string AuthenticateChallengedEventKey = "com.microsoft.kiota.authenticate_challenge_received";

private async Task<HttpResponseMessage> RetryCAEResponseIfRequired(HttpResponseMessage response, RequestInformation requestInfo, CancellationToken cancellationToken, string? claims, Activity? activityForAttributes)
{
using var span = activitySource?.StartActivity(nameof(RetryCAEResponseIfRequired));
if(response.StatusCode == HttpStatusCode.Unauthorized &&
string.IsNullOrEmpty(claims) && // avoid infinite loop, we only retry once
(requestInfo.Content?.CanSeek ?? true) &&
response.Headers.WwwAuthenticate?.FirstOrDefault(filterAuthHeader) is AuthenticationHeaderValue authHeader &&
authHeader.Parameter?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(static x => x.Trim())
.FirstOrDefault(static x => x.StartsWith(ClaimsKey, StringComparison.OrdinalIgnoreCase)) is string rawResponseClaims &&
caeValueRegex.Match(rawResponseClaims) is Match claimsMatch &&
claimsMatch.Groups.Count > 1 &&
claimsMatch.Groups[1].Value is string responseClaims)
(requestInfo.Content?.CanSeek ?? true))
{
span?.AddEvent(new ActivityEvent(AuthenticateChallengedEventKey));
activityForAttributes?.SetTag("http.retry_count", 1);
requestInfo.Content?.Seek(0, SeekOrigin.Begin);
await DrainAsync(response, cancellationToken).ConfigureAwait(false);
return await GetHttpResponseMessage(requestInfo, cancellationToken, activityForAttributes, responseClaims).ConfigureAwait(false);
AuthenticationHeaderValue? authHeader = null;
foreach(var header in response.Headers.WwwAuthenticate)
{
if(filterAuthHeader(header))
{
authHeader = header;
break;
}
}

if(authHeader is not null)
{
var authHeaderParameters = authHeader.Parameter?.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries);

string? rawResponseClaims = null;
if(authHeaderParameters != null)
{
foreach(var parameter in authHeaderParameters)
{
var trimmedParameter = parameter.Trim();
if(trimmedParameter.StartsWith(ClaimsKey, StringComparison.OrdinalIgnoreCase))
{
rawResponseClaims = trimmedParameter;
break;
}
}
}

if(rawResponseClaims != null &&
caeValueRegex.Match(rawResponseClaims) is Match claimsMatch &&
claimsMatch.Groups.Count > 1 &&
claimsMatch.Groups[1].Value is string responseClaims)
{
span?.AddEvent(new ActivityEvent(AuthenticateChallengedEventKey));
activityForAttributes?.SetTag("http.retry_count", 1);
requestInfo.Content?.Seek(0, SeekOrigin.Begin);
await DrainAsync(response, cancellationToken).ConfigureAwait(false);
return await GetHttpResponseMessage(requestInfo, cancellationToken, activityForAttributes, responseClaims).ConfigureAwait(false);
}
}
}
return response;
}

private void SetBaseUrlForRequestInformation(RequestInformation requestInfo)
{
IDictionaryExtensions.AddOrReplace(requestInfo.PathParameters, "baseurl", BaseUrl!);
Expand All @@ -527,6 +563,7 @@ private void SetBaseUrlForRequestInformation(RequestInformation requestInfo)
return result;
else throw new InvalidOperationException($"Could not convert the request information to a {typeof(T).Name}");
}

private HttpRequestMessage GetRequestMessageFromRequestInformation(RequestInformation requestInfo, Activity? activityForAttributes)
{
using var span = activitySource?.StartActivity(nameof(GetRequestMessageFromRequestInformation));
Expand All @@ -544,37 +581,42 @@ private HttpRequestMessage GetRequestMessageFromRequestInformation(RequestInform
Version = new Version(2, 0)
};

if(requestInfo.RequestOptions.Any())
if(requestInfo.RequestOptions != null)
#if NET5_0_OR_GREATER
{
requestInfo.RequestOptions.ToList().ForEach(x => message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(x.GetType().FullName!), x));
foreach (var option in requestInfo.RequestOptions)
message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(option.GetType().FullName!), option);
}
message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(typeof(ObservabilityOptions).FullName!), obsOptions);
#else
{
requestInfo.RequestOptions.ToList().ForEach(x => IDictionaryExtensions.TryAdd(message.Properties, x.GetType().FullName!, x));
foreach(var option in requestInfo.RequestOptions)
IDictionaryExtensions.TryAdd(message.Properties, option.GetType().FullName!, option);
}
IDictionaryExtensions.TryAdd(message.Properties!, typeof(ObservabilityOptions).FullName, obsOptions);
#endif

if(requestInfo.Content != null && requestInfo.Content != Stream.Null)
message.Content = new StreamContent(requestInfo.Content);
if(requestInfo.Headers?.Any() ?? false)
if(requestInfo.Headers != null)
foreach(var header in requestInfo.Headers)
if(!message.Headers.TryAddWithoutValidation(header.Key, header.Value) && message.Content != null)
message.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);// Try to add the headers we couldn't add to the HttpRequestMessage before to the HttpContent

if(message.Content != null)
{
if(message.Content.Headers.TryGetValues("Content-Length", out var contentLenValues) &&
contentLenValues.Any() &&
contentLenValues.First() is string contentLenValue &&
int.TryParse(contentLenValue, out var contentLenValueInt))
activityForAttributes?.SetTag("http.request_content_length", contentLenValueInt);
if(message.Content.Headers.TryGetValues("Content-Type", out var contentTypeValues) &&
contentTypeValues.Any() &&
contentTypeValues.First() is string contentTypeValue)
activityForAttributes?.SetTag("http.request_content_type", contentTypeValue);
if(message.Content.Headers.TryGetValues("Content-Length", out var contentLenValues))
{
var contentLenEnumerator = contentLenValues.GetEnumerator();
if(contentLenEnumerator.MoveNext() && int.TryParse(contentLenEnumerator.Current, out var contentLenValueInt))
activityForAttributes?.SetTag("http.request_content_length", contentLenValueInt);
}
if(message.Content.Headers.TryGetValues("Content-Type", out var contentTypeValues))
{
var contentTypeEnumerator = contentTypeValues.GetEnumerator();
if(contentTypeEnumerator.MoveNext())
activityForAttributes?.SetTag("http.request_content_type", contentTypeEnumerator.Current);
}
}
return message;
}
Expand Down
84 changes: 52 additions & 32 deletions src/KiotaClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System.Linq;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
Expand All @@ -26,8 +26,17 @@ public static class KiotaClientFactory
/// <returns>The <see cref="HttpClient"/> with the default middlewares.</returns>
public static HttpClient Create(HttpMessageHandler? finalHandler = null, IRequestOption[]? optionsForHandlers = null)
{
var defaultHandlers = CreateDefaultHandlers(optionsForHandlers);
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), defaultHandlers.ToArray());
var defaultHandlersEnumerable = CreateDefaultHandlers(optionsForHandlers);
int count = 0;
foreach(var _ in defaultHandlersEnumerable) count++;

var defaultHandlersArray = new DelegatingHandler[count];
int index = 0;
foreach(var handler2 in defaultHandlersEnumerable)
{
defaultHandlersArray[index++] = handler2;
}
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), defaultHandlersArray);
return handler != null ? new HttpClient(handler) : new HttpClient();
}

Expand All @@ -39,9 +48,16 @@ public static HttpClient Create(HttpMessageHandler? finalHandler = null, IReques
/// <returns>The <see cref="HttpClient"/> with the custom handlers.</returns>
public static HttpClient Create(IList<DelegatingHandler> handlers, HttpMessageHandler? finalHandler = null)
{
if(handlers == null || !handlers.Any())
if(handlers == null || handlers.Count == 0)
return Create(finalHandler);
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), handlers.ToArray());

DelegatingHandler[] handlersArray = new DelegatingHandler[handlers.Count];
for(int i = 0; i < handlers.Count; i++)
{
handlersArray[i] = handlers[i];
}

var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), handlersArray);
return handler != null ? new HttpClient(handler) : new HttpClient();
}

Expand All @@ -51,35 +67,39 @@ public static HttpClient Create(IList<DelegatingHandler> handlers, HttpMessageHa
/// <returns>A list of the default handlers used by the client.</returns>
public static IList<DelegatingHandler> CreateDefaultHandlers(IRequestOption[]? optionsForHandlers = null)
{
optionsForHandlers ??= [];

return new List<DelegatingHandler>
{
//add the default middlewares as they are ready, and add them to the list below as well

optionsForHandlers.OfType<UriReplacementHandlerOption>().FirstOrDefault() is UriReplacementHandlerOption uriReplacementOption
? new UriReplacementHandler<UriReplacementHandlerOption>(uriReplacementOption)
: new UriReplacementHandler<UriReplacementHandlerOption>(),

optionsForHandlers.OfType<RetryHandlerOption>().FirstOrDefault() is RetryHandlerOption retryHandlerOption
? new RetryHandler(retryHandlerOption)
: new RetryHandler(),

optionsForHandlers.OfType<RedirectHandlerOption>().FirstOrDefault() is RedirectHandlerOption redirectHandlerOption
? new RedirectHandler(redirectHandlerOption)
: new RedirectHandler(),
optionsForHandlers ??= Array.Empty<IRequestOption>();

optionsForHandlers.OfType<ParametersNameDecodingOption>().FirstOrDefault() is ParametersNameDecodingOption parametersNameDecodingOption
? new ParametersNameDecodingHandler(parametersNameDecodingOption)
: new ParametersNameDecodingHandler(),
UriReplacementHandlerOption? uriReplacementOption = null;
RetryHandlerOption? retryHandlerOption = null;
RedirectHandlerOption? redirectHandlerOption = null;
ParametersNameDecodingOption? parametersNameDecodingOption = null;
UserAgentHandlerOption? userAgentHandlerOption = null;
HeadersInspectionHandlerOption? headersInspectionHandlerOption = null;

optionsForHandlers.OfType<UserAgentHandlerOption>().FirstOrDefault() is UserAgentHandlerOption userAgentHandlerOption
? new UserAgentHandler(userAgentHandlerOption)
: new UserAgentHandler(),
foreach(var option in optionsForHandlers)
{
if(uriReplacementOption == null && option is UriReplacementHandlerOption uriOption)
uriReplacementOption = uriOption;
else if(retryHandlerOption == null && option is RetryHandlerOption retryOption)
retryHandlerOption = retryOption;
else if(redirectHandlerOption == null && option is RedirectHandlerOption redirectOption)
redirectHandlerOption = redirectOption;
else if(parametersNameDecodingOption == null && option is ParametersNameDecodingOption parametersOption)
parametersNameDecodingOption = parametersOption;
else if(userAgentHandlerOption == null && option is UserAgentHandlerOption userAgentOption)
userAgentHandlerOption = userAgentOption;
else if(headersInspectionHandlerOption == null && option is HeadersInspectionHandlerOption headersOption)
headersInspectionHandlerOption = headersOption;
}

optionsForHandlers.OfType<HeadersInspectionHandlerOption>().FirstOrDefault() is HeadersInspectionHandlerOption headersInspectionHandlerOption
? new HeadersInspectionHandler(headersInspectionHandlerOption)
: new HeadersInspectionHandler(),
return new List<DelegatingHandler>
{
uriReplacementOption != null ? new UriReplacementHandler<UriReplacementHandlerOption>(uriReplacementOption) : new UriReplacementHandler<UriReplacementHandlerOption>(),
retryHandlerOption != null ? new RetryHandler(retryHandlerOption) : new RetryHandler(),
redirectHandlerOption != null ? new RedirectHandler(redirectHandlerOption) : new RedirectHandler(),
parametersNameDecodingOption != null ? new ParametersNameDecodingHandler(parametersNameDecodingOption) : new ParametersNameDecodingHandler(),
userAgentHandlerOption != null ? new UserAgentHandler(userAgentHandlerOption) : new UserAgentHandler(),
headersInspectionHandlerOption != null ? new HeadersInspectionHandler(headersInspectionHandlerOption) : new HeadersInspectionHandler(),
};
}

Expand Down Expand Up @@ -109,7 +129,7 @@ public static IList<DelegatingHandler> CreateDefaultHandlers(IRequestOption[]? o
/// <returns>The created <see cref="DelegatingHandler"/>.</returns>
public static DelegatingHandler? ChainHandlersCollectionAndGetFirstLink(HttpMessageHandler? finalHandler, params DelegatingHandler[] handlers)
{
if(handlers == null || !handlers.Any()) return default;
if(handlers == null || handlers.Length == 0) return default;
var handlersCount = handlers.Length;
for(var i = 0; i < handlersCount; i++)
{
Expand Down
Loading
Loading