Use RawHttpMessageHandler and VssHttpRetryMessageHandler in ResultsHttpClient (#2908)

* Set an explicit timeout on ResultsHttpClient

* Hook up retry and standard message handler to ResultsHttpClient

* Remove explicit timeout constructor

* Fix linter
This commit is contained in:
Yang Cao
2023-10-06 12:01:28 -04:00
committed by GitHub
parent f672567acc
commit f47384b46e
4 changed files with 84 additions and 30 deletions

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security; using System.Security;
@@ -52,8 +53,8 @@ namespace GitHub.Runner.Common
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token) public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
{ {
var httpMessageHandler = HostContext.CreateHttpClientHandler(); this._resultsClient = CreateHttpClient(uri, token);
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
_token = token; _token = token;
if (!string.IsNullOrEmpty(liveConsoleFeedUrl)) if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
{ {
@@ -62,6 +63,26 @@ namespace GitHub.Runner.Common
} }
} }
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, public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {

View File

@@ -85,6 +85,35 @@ namespace GitHub.Runner.Sdk
VssCredentials credentials, VssCredentials credentials,
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
TimeSpan? timeout = 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(); RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
@@ -116,30 +145,7 @@ namespace GitHub.Runner.Sdk
// settings are applied to an HttpRequestMessage. // settings are applied to an HttpRequestMessage.
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture); settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler); return settings;
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;
} }
} }
} }

View File

@@ -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;
}
}
}

View File

@@ -109,7 +109,7 @@ namespace GitHub.Services.Common
lock (m_thisLock) lock (m_thisLock)
{ {
// Ensure that we attempt to use the most appropriate authentication mechanism by default. // 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); m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
} }
@@ -121,7 +121,8 @@ namespace GitHub.Services.Common
HttpResponseMessageWrapper responseWrapper; HttpResponseMessageWrapper responseWrapper;
Boolean lastResponseDemandedProxyAuth = false; 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 try
{ {
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -138,8 +139,12 @@ namespace GitHub.Services.Common
} }
// Let's start with sending a token // Let's start with sending a token
IssuedToken token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false); IssuedToken token = null;
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth); 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 // 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 // not what we want. By loading into a buffer up-front we bypass this behavior and there is