| File: EnhancedHttpRetryHelper.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Protocol\NuGet.Protocol.csproj (NuGet.Protocol) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using NuGet.Common; namespace NuGet.Protocol { /// <summary> /// Represents a helper class for determining if enhanced retry is enabled and what settings to use. /// </summary> internal class EnhancedHttpRetryHelper { /// <summary> /// The default delay in milliseconds between retries. /// </summary> public const int DefaultDelayMilliseconds = 1000; /// <summary> /// The default number of times to retry. /// </summary> public const int DefaultRetryCount = 6; /// <summary> /// The default value indicating whether or not to retry HTTP 429 responses. /// </summary> public const bool DefaultRetry429 = true; /// <summary> /// The default value indicating whether or not to observe Retry-After headers on responses. /// </summary> public const bool DefaultObserveRetryAfter = true; /// <summary> /// The default maximum delay in milliseconds to observe for a Retry-After header. /// </summary> public const int DefaultMaximumRetryAfterDelayInSeconds = 3600; /// <summary> /// The environment variable used to change the delay value. /// </summary> public const string DelayInMillisecondsEnvironmentVariableName = "NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS"; /// <summary> /// The environment variable used to change the retry value. /// </summary> public const string RetryCountEnvironmentVariableName = "NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT"; /// <summary> /// The environment variabled to to disable retrying HTTP 429 responses. /// </summary> public const string Retry429EnvironmentVariableName = "NUGET_RETRY_HTTP_429"; /// <summary> /// The envionment variable to disable observing Retry-After responses. /// </summary> public const string ObserveRetryAfterEnvironmentVariableName = "NUGET_OBSERVE_RETRY_AFTER"; /// <summary> /// The environment variable used to set maximum Retry-After delay period /// </summary> public const string MaximumRetryAfterDurationEnvironmentVariableName = "NUGET_MAX_RETRY_AFTER_DELAY_SECONDS"; private readonly IEnvironmentVariableReader _environmentVariableReader; private Lazy<int> _retryCount; private Lazy<(bool, int)> _delayInMilliseconds; private Lazy<bool> _retry429; private Lazy<bool> _observeRetryAfter; private Lazy<TimeSpan> _maxRetryAfterDelay; /// <summary> /// Initializes a new instance of the <see cref="EnhancedHttpRetryHelper" /> class. /// </summary> /// <param name="environmentVariableReader">A <see cref="IEnvironmentVariableReader" /> to use when reading environment variables.</param> /// <exception cref="ArgumentNullException"><paramref name="environmentVariableReader" /> is <see langword="null" />.</exception> public EnhancedHttpRetryHelper(IEnvironmentVariableReader environmentVariableReader) { _environmentVariableReader = environmentVariableReader ?? throw new ArgumentNullException(nameof(environmentVariableReader)); _retryCount = new Lazy<int>(() => GetIntFromEnvironmentVariableOrDefault(RetryCountEnvironmentVariableName, defaultValue: DefaultRetryCount, _environmentVariableReader).Item2); _delayInMilliseconds = new Lazy<(bool, int)>(() => GetIntFromEnvironmentVariableOrDefault(DelayInMillisecondsEnvironmentVariableName, defaultValue: DefaultDelayMilliseconds, _environmentVariableReader)); _retry429 = new Lazy<bool>(() => GetBoolFromEnvironmentVariable(Retry429EnvironmentVariableName, defaultValue: DefaultRetry429, _environmentVariableReader)); _observeRetryAfter = new Lazy<bool>(() => GetBoolFromEnvironmentVariable(ObserveRetryAfterEnvironmentVariableName, defaultValue: DefaultObserveRetryAfter, _environmentVariableReader)); _maxRetryAfterDelay = new Lazy<TimeSpan>(() => { int maxRetryAfterDelay = GetIntFromEnvironmentVariableOrDefault(MaximumRetryAfterDurationEnvironmentVariableName, defaultValue: DefaultMaximumRetryAfterDelayInSeconds, _environmentVariableReader).Item2; return TimeSpan.FromSeconds(maxRetryAfterDelay); }); } /// <summary> /// Gets a value indicating the maximum number of times to retry. /// The default value is 6, see <see cref="DefaultRetryCount" />. /// </summary> internal int RetryCountOrDefault => _retryCount.Value; /// <summary> /// Gets a value indicating the delay in milliseconds to wait before retrying a connection. /// The default value is 1000, <see cref="DefaultDelayMilliseconds" />. /// </summary> internal int DelayInMillisecondsOrDefault => _delayInMilliseconds.Value.Item2; /// <summary> /// Gets a value indicating the delay in milliseconds to wait before retrying a connection.| /// Will only have a value if the environment variable is set, otherwise it will be <see langword="null" />. /// </summary> internal int? DelayInMilliseconds => _delayInMilliseconds.Value.Item1 ? _delayInMilliseconds.Value.Item2 : null; /// <summary> /// Gets a value indicating whether or not retryable HTTP 4xx responses should be retried. /// Default is <see langword="true" />, <see cref="DefaultObserveRetryAfter"/>. /// </summary> internal bool Retry429OrDefault => _retry429.Value; /// <summary> /// Gets a value indicating whether or not to observe the Retry-After header on HTTP responses. /// Default is <see langword="true" />, <see cref="DefaultObserveRetryAfter"/>. /// </summary> internal bool ObserveRetryAfterOrDefault => _observeRetryAfter.Value; /// <summary> /// Gets a value indicating the maximum delay to observe for a Retry-After header. /// Default is 1 hour, <see cref="DefaultMaximumRetryAfterDelayInSeconds" />. /// </summary> internal TimeSpan MaxRetryAfterDelayOrDefault => _maxRetryAfterDelay.Value; /// <summary> /// Gets a <see cref="bool" /> value from the specified environment variable. /// </summary> /// <param name="variableName">The name of the environment variable to get the value.</param> /// <param name="defaultValue">The default value to return if the environment variable is not defined or is not a valid <see cref="bool" />.</param> /// <param name="environmentVariableReader">An <see cref="IEnvironmentVariableReader" /> to use when reading the environment variable.</param> /// <returns>The value of the specified as a <see cref="bool" /> if the specified environment variable is defined and is a valid value for <see cref="bool" />.</returns> private static bool GetBoolFromEnvironmentVariable(string variableName, bool defaultValue, IEnvironmentVariableReader environmentVariableReader) { try { if (bool.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out bool parsedValue)) { return parsedValue; } } catch (Exception) { } return defaultValue; } /// <summary> /// Gets an <see cref="int" /> value from the specified environment variable. /// </summary> /// <param name="variableName">The name of the environment variable to get the value.</param> /// <param name="defaultValue">The default value to return if the environment variable is not defined or is not a valid <see cref="int" />.</param> /// <param name="environmentVariableReader">An <see cref="IEnvironmentVariableReader" /> to use when reading the environment variable.</param> /// <returns> A tuple containing a <see cref="bool" /> indicating if the value was provided through an environment variable parsed and a valid int. /// the value of the specified as a <see cref="int" /> if the specified environment variable /// is defined and is a valid value for <see cref="int" />.</returns> private static (bool, int) GetIntFromEnvironmentVariableOrDefault(string variableName, int defaultValue, IEnvironmentVariableReader environmentVariableReader) { try { if (int.TryParse(environmentVariableReader.GetEnvironmentVariable(variableName), out int parsedValue) && parsedValue >= 0) { return (true, parsedValue); } } catch (Exception) { } return (false, defaultValue); } } }