Skip to content

Commit

Permalink
Use RawHttpMessageHandler and VssHttpRetryMessageHandler in ResultsHt…
Browse files Browse the repository at this point in the history
…tpClient (#2908)

* Set an explicit timeout on ResultsHttpClient

* Hook up retry and standard message handler to ResultsHttpClient

* Remove explicit timeout constructor

* Fix linter
  • Loading branch information
yacaovsnc committed Oct 6, 2023
1 parent f672567 commit f47384b
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 30 deletions.
25 changes: 23 additions & 2 deletions src/Runner.Common/ResultsServer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.WebSockets;
using System.Security;
Expand Down Expand Up @@ -52,8 +53,8 @@ public sealed class ResultServer : RunnerService, IResultsServer

public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
{
var httpMessageHandler = HostContext.CreateHttpClientHandler();
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
this._resultsClient = CreateHttpClient(uri, token);

_token = token;
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
{
Expand All @@ -62,6 +63,26 @@ public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string t
}
}

public ResultsHttpClient CreateHttpClient(Uri uri, string token)
{
// Using default 100 timeout
RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null);

// Create retry handler
IEnumerable<DelegatingHandler> delegatingHandlers = new List<DelegatingHandler>();
if (settings.MaxRetryRequest > 0)
{
delegatingHandlers = new DelegatingHandler[] { new VssHttpRetryMessageHandler(settings.MaxRetryRequest) };
}

// Setup RawHttpMessageHandler without credentials
var httpMessageHandler = new RawHttpMessageHandler(new NoOpCredentials(null), settings);

var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers);

return new ResultsHttpClient(uri, pipeline, token, disposeHandler: true);
}

public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
CancellationToken cancellationToken)
{
Expand Down
54 changes: 30 additions & 24 deletions src/Runner.Sdk/Util/VssUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ public static RawConnection CreateRawConnection(
VssCredentials credentials,
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
TimeSpan? timeout = null)
{
RawClientHttpRequestSettings settings = GetHttpRequestSettings(timeout);
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
return connection;
}

public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
{
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));

if (serviceEndpoint.Authorization.Parameters.Count == 0)
{
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
}

VssCredentials credentials = null;
string accessToken;
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
{
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
}

return credentials;
}

public static RawClientHttpRequestSettings GetHttpRequestSettings(TimeSpan? timeout = null)
{
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();

Expand Down Expand Up @@ -116,30 +145,7 @@ public static RawConnection CreateRawConnection(
// settings are applied to an HttpRequestMessage.
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);

RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
return connection;
}

public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
{
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));

if (serviceEndpoint.Authorization.Parameters.Count == 0)
{
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
}

VssCredentials credentials = null;
string accessToken;
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
{
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
}

return credentials;
return settings;
}
}
}
22 changes: 22 additions & 0 deletions src/Sdk/Common/Common/Authentication/NoOpCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace GitHub.Services.Common
{
// Set of classes used to bypass token operations
// Results Service and External services follow a different auth model but
// we are required to pass in a credentials object to create a RawHttpMessageHandler
public class NoOpCredentials : FederatedCredential
{
public NoOpCredentials(IssuedToken initialToken) : base(initialToken)
{
}

public override VssCredentialsType CredentialType { get; }
protected override IssuedTokenProvider OnCreateTokenProvider(Uri serverUrl, IHttpResponse response)
{
return null;
}
}
}
13 changes: 9 additions & 4 deletions src/Sdk/Common/Common/RawHttpMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ protected override async Task<HttpResponseMessage> SendAsync(
lock (m_thisLock)
{
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
if (m_tokenProvider == null)
if (m_tokenProvider == null && !(this.Credentials is NoOpCredentials))
{
m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
}
Expand All @@ -121,7 +121,8 @@ protected override async Task<HttpResponseMessage> SendAsync(
HttpResponseMessageWrapper responseWrapper;

Boolean lastResponseDemandedProxyAuth = false;
Int32 retries = m_maxAuthRetries;
// do not retry if we cannot recreate tokens
Int32 retries = this.Credentials is NoOpCredentials ? 0 : m_maxAuthRetries;
try
{
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
Expand All @@ -138,8 +139,12 @@ protected override async Task<HttpResponseMessage> SendAsync(
}

// Let's start with sending a token
IssuedToken token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
IssuedToken token = null;
if (m_tokenProvider != null)
{
token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
}

// The WinHttpHandler will chunk any content that does not have a computed length which is
// not what we want. By loading into a buffer up-front we bypass this behavior and there is
Expand Down

0 comments on commit f47384b

Please sign in to comment.