<PackageReference Include="Relativity.Transfer.Client" Version="7.0.11" />

RestClient

public class RestClient
using Polly; using Relativity.Transfer.Resources; using System; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Relativity.Transfer { public class RestClient { private const HttpStatusCode NoHttpStatusCode = (HttpStatusCode)0; private const int HttpRequestGet = 1; private const int HttpRequestPost = 2; private const int HttpRequestDelete = 3; private readonly HttpConnectionInfo connectionInfo; private readonly ITransferLog transferLog; public int MaxRetryAttempts { get; set; } public double TimeoutSeconds { get; } public RestClient(HttpConnectionInfo connectionInfo, ITransferLog log) : this(connectionInfo, log, 300, 5) { } public RestClient(HttpConnectionInfo connectionInfo, ITransferLog log, double timeoutSeconds, int maxRetryAttempts) { if (connectionInfo == null) throw new ArgumentNullException("connectionInfo"); if (log == null) throw new ArgumentNullException("log"); this.connectionInfo = connectionInfo; transferLog = log; TimeoutSeconds = timeoutSeconds; MaxRetryAttempts = maxRetryAttempts; } public async Task<T> RequestDeleteAsync<T>(string endpoint, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token) where T : class, new { return SerializationHelper.DeserializeFromJson<T>(await RequestAsync(3, endpoint, string.Empty, sleepDurationProvider, onRetry, onEndpointErrorTitle, onEndpointErrorMessage, token).ConfigureAwait(false)); } public async Task<T> RequestGetAsync<T>(string endpoint, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token) where T : class, new { return SerializationHelper.DeserializeFromJson<T>(await RequestAsync(1, endpoint, string.Empty, sleepDurationProvider, onRetry, onEndpointErrorTitle, onEndpointErrorMessage, token).ConfigureAwait(false)); } public async Task<T> RequestPostAsync<T>(string endpoint, string content, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token) where T : class, new { return SerializationHelper.DeserializeFromJson<T>(await RequestAsync(2, endpoint, content, sleepDurationProvider, onRetry, onEndpointErrorTitle, onEndpointErrorMessage, token).ConfigureAwait(false)); } public Task<string> RequestJsonPostAsync(string endpoint, string content, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token) { return RequestAsync(2, endpoint, content, sleepDurationProvider, onRetry, onEndpointErrorTitle, onEndpointErrorMessage, token); } private async Task<string> RequestAsync(int method, string endpoint, string content, Func<int, TimeSpan> sleepDurationProvider, Action<Exception, TimeSpan, Context> onRetry, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token) { return await RetrySyntaxAsync.WaitAndRetryAsync(Policy.Handle<TransferException>((Func<TransferException, bool>)((TransferException e) => !e.Fatal)), MaxRetryAttempts, sleepDurationProvider, onRetry).ExecuteAsync<string>((Func<CancellationToken, Task<string>>)(async (CancellationToken cancellationToken) => await ExecuteCall(method, endpoint, content, onEndpointErrorTitle, onEndpointErrorMessage, token, true)), token).ConfigureAwait(false); } private async Task<string> ExecuteCall(int method, string endpoint, string content, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, CancellationToken token, bool forceTokenRefreshRetry = true) { ServicePointManager.SecurityProtocol = (SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12); using (HttpClient client = new HttpClient()) { client.BaseAddress = connectionInfo.Host; client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Add("X-CSRF-Header", string.Empty); string authenticationHeader = connectionInfo.Credential.GetAuthenticationHeader(); if (string.IsNullOrEmpty(authenticationHeader)) throw CreateCredentialNotSupportedTransferException(endpoint, onEndpointErrorTitle, connectionInfo.Credential); client.DefaultRequestHeaders.Add("Authorization", authenticationHeader); if (TimeoutSeconds > 0) client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds); using (StringContent stringContent = new StringContent((!string.IsNullOrEmpty(content)) ? content : string.Empty, Encoding.UTF8, "application/json")) { Uri endpointUri = new Uri(endpoint, UriKind.Relative); string methodName = string.Empty; try { HttpResponseMessage response = new HttpResponseMessage(); string jsonResponse = string.Empty; try { switch (method) { case 1: { methodName = "GET"; transferLog.LogDebug("Preparing to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint...", endpointUri, methodName); HttpResponseMessage httpResponseMessage3 = await client.GetAsync(endpointUri, token).ConfigureAwait(false); response = httpResponseMessage3; break; } case 2: { methodName = "POST"; transferLog.LogDebug("Preparing to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint...", endpointUri, methodName); HttpResponseMessage httpResponseMessage2 = await client.PostAsync(endpointUri, stringContent, token).ConfigureAwait(false); response = httpResponseMessage2; break; } case 3: { methodName = "DELETE"; transferLog.LogDebug("Preparing to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint...", endpointUri, methodName); HttpResponseMessage httpResponseMessage = await client.DeleteAsync(endpointUri, token).ConfigureAwait(false); response = httpResponseMessage; break; } default: throw new NotSupportedException($"""{method}"""); } if (forceTokenRefreshRetry && response.StatusCode == HttpStatusCode.Unauthorized) { RefreshCredentials(methodName); return await ExecuteCall(method, endpoint, content, onEndpointErrorTitle, onEndpointErrorMessage, token, false).ConfigureAwait(false); } jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); response.EnsureSuccessStatusCode(); transferLog.LogDebug("Successfully called the HTTP '{Endpoint}' ({HttpMethod}) endpoint.", endpointUri, methodName); return jsonResponse; } catch (OperationCanceledException e4) { transferLog.LogInformation(e4, "The user cancelled the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation.", endpointUri, methodName); throw; } catch (Exception ex) { throw CreateExtendedTransferException(exception: ex, fatal: GlobalSettings.Instance.FatalHttpStatusCodes.Any((HttpStatusCode candidate) => candidate == response.StatusCode), endpoint: endpointUri.ToString(), methodName: methodName, statusCode: response.StatusCode, json: jsonResponse, onEndpointErrorTitle: onEndpointErrorTitle, onEndpointErrorMessage: onEndpointErrorMessage); } } catch (TaskCanceledException e2) { if (token.IsCancellationRequested) throw; throw CreateTimeoutTransferException(endpoint, methodName, e2, onEndpointErrorTitle, onEndpointErrorMessage); } catch (HttpRequestException e) { WebException webException = e.InnerException as WebException; if (webException != null) { if (GlobalSettings.Instance.FatalWebExceptionStatusCodes.Any((WebExceptionStatus x) => x == webException.Status)) throw CreateExtendedTransferException(endpoint, methodName, string.Empty, webException, onEndpointErrorTitle, onEndpointErrorMessage, true); HttpWebResponse response2 = webException.Response as HttpWebResponse; if (response2 != null) throw CreateExtendedTransferException(fatal: GlobalSettings.Instance.FatalHttpStatusCodes.Any((HttpStatusCode x) => x == response2.StatusCode), endpoint: endpoint, methodName: methodName, statusCode: response2.StatusCode, json: string.Empty, exception: webException, onEndpointErrorTitle: onEndpointErrorTitle, onEndpointErrorMessage: onEndpointErrorMessage); throw CreateExtendedTransferException(endpoint, methodName, string.Empty, webException, onEndpointErrorTitle, onEndpointErrorMessage, false); } throw CreateExtendedTransferException(endpoint, methodName, (HttpStatusCode)0, string.Empty, e, onEndpointErrorTitle, onEndpointErrorMessage, false); } } } } private void RefreshCredentials(string methodName) { transferLog.LogInformation("Authorization issue occured. Trying to auto refresh credentials and make next call to {method}", methodName); connectionInfo.Credential.RefreshCredentials(); } private TransferException CreateExtendedTransferException(string endpoint, string methodName, HttpStatusCode statusCode, string json, Exception exception, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, bool fatal) { transferLog.LogError(exception, fatal ? "Fatal attempt to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation. HTTP StatusCode={StatusCode}, Response={JsonResponse}" : "Failed to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation. HTTP StatusCode={StatusCode}, Response={JsonResponse}", endpoint, methodName, statusCode, json); string text = onEndpointErrorTitle(statusCode); if (string.IsNullOrEmpty(text)) text = CoreStrings.NoEndpointProvided; string text2 = onEndpointErrorMessage(statusCode); if (string.IsNullOrEmpty(text2)) text2 = CoreStrings.NoMessageProvided; string text3 = GlobalSettings.Instance.FatalHttpStatusCodeDetailedMessage(statusCode); if (string.IsNullOrEmpty(text3)) text3 = exception.Message; if (string.IsNullOrEmpty(text3)) text3 = CoreStrings.NoMessageProvided; string text4 = string.Format(CultureInfo.CurrentCulture, CoreStrings.HttpExceptionMessage, text, methodName, (int)statusCode, text2, text3); if (statusCode == (HttpStatusCode)0) text4 = string.Format(CultureInfo.CurrentCulture, CoreStrings.HttpNoStatusExceptionMessage, text, methodName, text2, text3); text4 = text4.TrimEnd(Array.Empty<char>()); return new TransferException(text4, exception, fatal); } private TransferException CreateExtendedTransferException(string endpoint, string methodName, string json, WebException exception, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage, bool fatal) { transferLog.LogError(exception, fatal ? "Fatal attempt to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation. Web Response Status={WebResponseStatus}, Response={JsonResponse}" : "Failed to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation. Web Response Status={WebResponseStatus}, Response={JsonResponse}", endpoint, methodName, exception.Status, json); string text = onEndpointErrorTitle((HttpStatusCode)0); if (string.IsNullOrEmpty(text)) text = CoreStrings.NoEndpointProvided; string text2 = onEndpointErrorMessage((HttpStatusCode)0); if (string.IsNullOrEmpty(text2)) text2 = CoreStrings.NoMessageProvided; string text3 = GlobalSettings.Instance.FatalWebExceptionStatusCodeDetailedMessage(exception.Status); if (string.IsNullOrEmpty(text3)) text3 = exception.Message; if (string.IsNullOrEmpty(text3)) text3 = CoreStrings.NoMessageProvided; string text4 = string.Format(CultureInfo.CurrentCulture, CoreStrings.WebExceptionMessage, text, methodName, (int)exception.Status, text2, text3); text4 = text4.TrimEnd(Array.Empty<char>()); return new TransferException(text4, exception, fatal); } private TransferException CreateTimeoutTransferException(string endpoint, string methodName, Exception exception, Func<HttpStatusCode, string> onEndpointErrorTitle, Func<HttpStatusCode, string> onEndpointErrorMessage) { transferLog.LogError(exception, "Failed to call the HTTP '{Endpoint}' ({HttpMethod}) endpoint operation because it exceeded the {HttpTimeoutSeconds} second timeout.", endpoint, methodName, TimeoutSeconds); string text = onEndpointErrorTitle(HttpStatusCode.RequestTimeout); if (string.IsNullOrEmpty(text)) text = CoreStrings.NoEndpointProvided; string text2 = onEndpointErrorMessage(HttpStatusCode.RequestTimeout); if (string.IsNullOrEmpty(text2)) text2 = CoreStrings.NoMessageProvided; string text3 = exception.Message; if (string.IsNullOrEmpty(text3)) text3 = CoreStrings.NoMessageProvided; string text4 = string.Format(CultureInfo.CurrentCulture, CoreStrings.HttpTimeoutExceptionMessage, text, methodName, TimeoutSeconds, text2, text3); text4 = text4.TrimEnd(Array.Empty<char>()); return new TransferException(text4, exception, false); } private TransferException CreateCredentialNotSupportedTransferException(string endpoint, Func<HttpStatusCode, string> onEndpointErrorTitle, IHttpCredential credential) { transferLog.LogError("Failed to call the HTTP '{Endpoint}' endpoint operation because the supplied Transfer API credential object '{CredentialType}' is not supported.", endpoint, credential.GetType()); string text = onEndpointErrorTitle(HttpStatusCode.Unauthorized); if (string.IsNullOrEmpty(text)) text = CoreStrings.NoEndpointProvided; string text2 = string.Format(CultureInfo.CurrentCulture, CoreStrings.HttpCredentialNotSupportedExceptionMessage, text, credential.GetType().ToString()); text2 = text2.TrimEnd(Array.Empty<char>()); return new TransferException(text2, true); } } }