/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using Amazon.Runtime.Internal.Auth;
using Amazon.Util;
using System.Globalization;
#if NETSTANDARD
using System.Runtime.InteropServices;
#endif
namespace Amazon.Runtime
{
///
/// This class is the base class of all the configurations settings to connect
/// to a service.
///
public abstract partial class ClientConfig : IClientConfig
{
// Represents infinite timeout. http://msdn.microsoft.com/en-us/library/system.threading.timeout.infinite.aspx
internal static readonly TimeSpan InfiniteTimeout = TimeSpan.FromMilliseconds(-1);
// Represents max timeout.
public static readonly TimeSpan MaxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
private RegionEndpoint regionEndpoint = null;
private bool probeForRegionEndpoint = true;
private bool throttleRetries = true;
private bool useHttp = false;
private string serviceURL = null;
private string authRegion = null;
private string authServiceName = null;
private string signatureVersion = "4";
private SigningAlgorithm signatureMethod = SigningAlgorithm.HmacSHA256;
private int maxErrorRetry = 4;
private bool readEntireResponse = false;
private bool logResponse = false;
private int bufferSize = AWSSDKUtils.DefaultBufferSize;
private long progressUpdateInterval = AWSSDKUtils.DefaultProgressUpdateInterval;
private bool resignRetries = false;
private ICredentials proxyCredentials;
private bool logMetrics = AWSConfigs.LoggingConfig.LogMetrics;
private bool disableLogging = false;
private TimeSpan? timeout = null;
private bool allowAutoRedirect = true;
private bool useDualstackEndpoint = false;
private TimeSpan? readWriteTimeout = null;
private bool disableHostPrefixInjection = false;
private bool? endpointDiscoveryEnabled = null;
private int endpointDiscoveryCacheLimit = 1000;
#if BCL
private readonly TcpKeepAlive tcpKeepAlive = new TcpKeepAlive();
#endif
///
/// Gets Service Version
///
public abstract string ServiceVersion
{
get;
}
///
/// Gets and sets of the signatureMethod property.
///
public SigningAlgorithm SignatureMethod
{
get { return this.signatureMethod; }
set { this.signatureMethod = value; }
}
///
/// Gets and sets of the SignatureVersion property.
///
/// Note: This property exists for backward compatibility but is no longer
/// used by any service other than S3.
///
public string SignatureVersion
{
get { return this.signatureVersion; }
set { this.signatureVersion = value; }
}
///
/// Gets and sets of the UserAgent property.
///
public abstract string UserAgent { get; }
///
/// Gets and sets the RegionEndpoint property. The region constant that
/// determines the endpoint to use.
///
/// Setting this property to null will force the SDK to recalculate the
/// RegionEndpoint value based on App/WebConfig, environment variables,
/// profile, etc.
///
public RegionEndpoint RegionEndpoint
{
get
{
#if BCL || NETSTANDARD
if (probeForRegionEndpoint)
{
RegionEndpoint = GetDefaultRegionEndpoint();
this.probeForRegionEndpoint = false;
}
#endif
return this.regionEndpoint;
}
set
{
this.serviceURL = null;
this.regionEndpoint = value;
this.probeForRegionEndpoint = this.regionEndpoint == null;
}
}
///
/// The constant used to lookup in the region hash the endpoint.
///
public abstract string RegionEndpointServiceName
{
get;
}
///
/// Gets and sets of the ServiceURL property.
/// This is an optional property; change it
/// only if you want to try a different service
/// endpoint.
///
public string ServiceURL
{
get { return this.serviceURL; }
set
{
this.regionEndpoint = null;
this.probeForRegionEndpoint = false;
this.serviceURL = value;
}
}
///
/// Gets and sets the UseHttp.
/// If this property is set to true, the client attempts
/// to use HTTP protocol, if the target endpoint supports it.
/// By default, this property is set to false.
///
public bool UseHttp
{
get { return this.useHttp; }
set { this.useHttp = value; }
}
///
/// Given this client configuration, return a string form ofthe service endpoint url.
///
public virtual string DetermineServiceURL()
{
string url;
if (this.ServiceURL != null)
{
url = this.ServiceURL;
}
else
{
url = GetUrl(this.RegionEndpoint, this.RegionEndpointServiceName, this.UseHttp, this.UseDualstackEndpoint);
}
return url;
}
internal static string GetUrl(RegionEndpoint regionEndpoint, string regionEndpointServiceName, bool useHttp, bool useDualStack)
{
var endpoint = regionEndpoint.GetEndpointForService(regionEndpointServiceName, useDualStack);
string url = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}{1}", useHttp ? "http://" : "https://", endpoint.Hostname)).AbsoluteUri;
return url;
}
///
/// Gets and sets the AuthenticationRegion property.
/// Used in AWS4 request signing, this is an optional property;
/// change it only if the region cannot be determined from the
/// service endpoint.
///
public string AuthenticationRegion
{
get { return this.authRegion; }
set { this.authRegion = value; }
}
///
/// Gets and sets the AuthenticationServiceName property.
/// Used in AWS4 request signing, this is the short-form
/// name of the service being called.
///
public string AuthenticationServiceName
{
get { return this.authServiceName; }
set { this.authServiceName = value; }
}
///
/// Gets and sets of the MaxErrorRetry property.
///
public int MaxErrorRetry
{
get { return this.maxErrorRetry; }
set { this.maxErrorRetry = value; }
}
///
/// Gets and sets the LogResponse property.
/// If this property is set to true, the service response is logged.
/// The size of response being logged is controlled by the AWSConfigs.LoggingConfig.LogResponsesSizeLimit property.
///
public bool LogResponse
{
get { return this.logResponse; }
set { this.logResponse = value; }
}
///
/// Gets and sets the ReadEntireResponse property.
/// NOTE: This property does not effect response processing and is deprecated.
/// To enable response logging, the ClientConfig.LogResponse and AWSConfigs.LoggingConfig
/// properties can be used.
///
[Obsolete("This property does not effect response processing and is deprecated." +
"To enable response logging, the ClientConfig.LogResponse and AWSConfigs.LoggingConfig.LogResponses properties can be used.")]
public bool ReadEntireResponse
{
get { return this.readEntireResponse; }
set { this.readEntireResponse = value; }
}
///
/// Gets and Sets the BufferSize property.
/// The BufferSize controls the buffer used to read in from input streams and write
/// out to the request.
///
public int BufferSize
{
get { return this.bufferSize; }
set { this.bufferSize = value; }
}
///
///
/// Gets or sets the interval at which progress update events are raised
/// for upload operations. By default, the progress update events are
/// raised at every 100KB of data transferred.
///
///
/// If the value of this property is set less than ClientConfig.BufferSize,
/// progress updates events will be raised at the interval specified by ClientConfig.BufferSize.
///
///
public long ProgressUpdateInterval
{
get { return progressUpdateInterval; }
set { progressUpdateInterval = value; }
}
///
/// Flag on whether to resign requests on retry or not.
/// For Amazon S3 and Amazon Glacier this value will always be set to true.
///
public bool ResignRetries
{
get { return this.resignRetries; }
set { this.resignRetries = value; }
}
///
/// This flag controls if .NET HTTP infrastructure should follow redirection
/// responses (e.g. HTTP 307 - temporary redirect).
///
public bool AllowAutoRedirect
{
get
{
return this.allowAutoRedirect;
}
set
{
this.allowAutoRedirect = value;
}
}
///
/// Flag on whether to log metrics for service calls.
///
/// This can be set in the application's configs, as below:
///
/// <?xml version="1.0" encoding="utf-8" ?>
/// <configuration>
/// <appSettings>
/// <add key="AWSLogMetrics" value"true"/>
/// </appSettings>
/// </configuration>
///
///
public bool LogMetrics
{
get { return this.logMetrics; }
set { this.logMetrics = value; }
}
///
/// Gets and sets the DisableLogging. If true logging for this client will be disabled.
///
public bool DisableLogging
{
get { return this.disableLogging; }
set { this.disableLogging = value; }
}
///
/// Credentials to use with a proxy.
///
public ICredentials ProxyCredentials
{
get
{
if(this.proxyCredentials == null &&
(!string.IsNullOrEmpty(AWSConfigs.ProxyConfig.Username) ||
!string.IsNullOrEmpty(AWSConfigs.ProxyConfig.Password)))
{
return new NetworkCredential(AWSConfigs.ProxyConfig.Username, AWSConfigs.ProxyConfig.Password ?? string.Empty);
}
return this.proxyCredentials;
}
set { this.proxyCredentials = value; }
}
#if BCL
///
/// Specifies the TCP keep-alive values to use for service requests.
///
public TcpKeepAlive TcpKeepAlive
{
get { return this.tcpKeepAlive; }
}
#endif
#region Constructor
public ClientConfig()
{
Initialize();
}
#endregion
protected virtual void Initialize()
{
}
#if UNITY
///
/// Overrides the default request timeout value.
/// On Unity platform this value is not used as Unity HTTP client does not support timeouts.
///
#endif
///
///
/// If the value is set, the value is assigned to the Timeout property of the HttpWebRequest/HttpClient object used
/// to send requests.
///
///
/// Please specify a timeout value only if the operation will not complete within the default intervals
/// specified for an HttpWebRequest/HttpClient.
///
///
/// The timeout specified is null.
/// The timeout specified is less than or equal to zero and is not Infinite.
///
///
public TimeSpan? Timeout
{
get { return this.timeout; }
set
{
ValidateTimeout(value);
this.timeout = value;
}
}
///
/// Configures the endpoint calculation for a service to go to a dual stack (ipv6 enabled) endpoint
/// for the configured region.
///
///
/// Note: AWS services are enabling dualstack endpoints over time. It is your responsibility to check
/// that the service actually supports a dualstack endpoint in the configured region before enabling
/// this option for a service.
///
public bool UseDualstackEndpoint
{
get { return useDualstackEndpoint; }
set { useDualstackEndpoint = value; }
}
///
/// Enable or disable the Retry Throttling feature by setting the ThrottleRetries flag to True/False resepctively.
/// Retry Throttling is a feature that intelligently throttles retry attempts when a large precentage of requests
/// are failing and retries are unsuccessful as well. In such situations the allotted retry capacity for the service URL
/// will be drained until requests start to succeed again. Once the requisite capacity is available, retries would
/// be permitted again. When retries are throttled, the service enters a fail-fast behaviour as the traditional retry attempt
/// for the request would be circumvented. Hence, errors will resurface quickly. This will result in a greated number of exceptions
/// but prevents requests being tied up in unsuccessful retry attempts.
/// Note: Retry Throttling is enabled by default. Set the ThrottleRetries flag to false to switch off this feature.
///
public bool ThrottleRetries
{
get { return throttleRetries; }
set { throttleRetries = value; }
}
///
/// Enable or disable the Nagle algorithm on the underlying http
/// client.
///
/// This method is not intended to be called by consumers of the AWS SDK for .NET
///
///
public void SetUseNagleIfAvailable(bool useNagle)
{
#if BCL
this.UseNagleAlgorithm = useNagle;
#endif
}
///
/// Performs validation on this config object.
/// Throws exception if any of the required values are missing/invalid.
///
public virtual void Validate()
{
if (RegionEndpoint == null && string.IsNullOrEmpty(this.ServiceURL))
throw new AmazonClientException("No RegionEndpoint or ServiceURL configured");
#if BCL
if (TcpKeepAlive.Enabled)
{
ValidateTcpKeepAliveTimeSpan(TcpKeepAlive.Timeout, "TcpKeepAlive.Timeout");
ValidateTcpKeepAliveTimeSpan(TcpKeepAlive.Interval, "TcpKeepAlive.Interval");
}
#endif
}
///
/// Returns the current UTC now after clock correction for this endpoint.
///
[Obsolete("Please use CorrectClockSkew.GetCorrectedUtcNowForEndpoint(string endpoint) instead.", false)]
public DateTime CorrectedUtcNow
{
get
{
return CorrectClockSkew.GetCorrectedUtcNowForEndpoint(DetermineServiceURL());
}
}
///
/// The calculated clock skew correction for a specific endpoint, if there is one.
/// This field will be set if a service call resulted in an exception
/// and the SDK has determined that there is a difference between local
/// and server times.
///
/// If is set to true, this
/// value will still be set to the correction, but it will not be used by the
/// SDK and clock skew errors will not be retried.
///
public TimeSpan ClockOffset
{
get
{
if (AWSConfigs.ManualClockCorrection.HasValue)
{
return AWSConfigs.ManualClockCorrection.Value;
}
else
{
string endpoint = DetermineServiceURL();
return CorrectClockSkew.GetClockCorrectionForEndpoint(endpoint);
}
}
}
///
/// Gets and sets the DisableHostPrefixInjection flag. If true, host prefix injection will be disabled for this client, the default value of this flag is false.
/// Host prefix injection prefixes the service endpoint with request members from APIs which use this feature.
/// Example: for a hostPrefix of "foo-name." and a endpoint of "service.region.amazonaws.com" the default behavior is to
/// prefix the endpoint with the hostPrefix resulting in a final endpoint of "foo-name.service.region.amazonaws.com". Setting
/// DisableHostPrefixInjection to true will disable hostPrefix injection resulting in a final endpoint of
/// "service.region.amazonaws.com" regardless of the value of hostPrefix. E.g. You may want to disable host prefix injection for testing against a local mock endpoint.
///
public bool DisableHostPrefixInjection
{
get { return this.disableHostPrefixInjection; }
set { this.disableHostPrefixInjection = value; }
}
///
/// Returns the flag indicating if endpoint discovery should be enabled or disabled for operations that are not required to use endpoint discovery.
///
public bool EndpointDiscoveryEnabled
{
get
{
if (!this.endpointDiscoveryEnabled.HasValue)
{
var endpointDiscoveryEnabled = FallbackEndpointDiscoveryEnabledFactory.GetEnabled();
if (endpointDiscoveryEnabled != null)
{
return endpointDiscoveryEnabled.Value;
}
else
{
return false;
}
}
return this.endpointDiscoveryEnabled.Value;
}
set { this.endpointDiscoveryEnabled = value; }
}
///
/// Returns the maximum number of discovered endpoints that can be stored within the cache for the client. The default limit is 1000 cache entries.
///
public int EndpointDiscoveryCacheLimit
{
get { return this.endpointDiscoveryCacheLimit; }
set { this.endpointDiscoveryCacheLimit = value; }
}
///
/// Throw an exception if the boxed TimeSpan parameter doesn't have a value or is out of range.
///
public static void ValidateTimeout(TimeSpan? timeout)
{
if (!timeout.HasValue)
{
throw new ArgumentNullException("timeout");
}
if (timeout != InfiniteTimeout && (timeout <= TimeSpan.Zero || timeout > MaxTimeout))
{
throw new ArgumentOutOfRangeException("timeout");
}
}
#if BCL
private static void ValidateTcpKeepAliveTimeSpan(TimeSpan? value, string paramName)
{
if (!value.HasValue)
{
throw new ArgumentNullException(paramName);
}
if (value > MaxTimeout || (int)value.Value.TotalMilliseconds <= 0)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
#endif
///
/// Returns the request timeout value if its value is set,
/// else returns client timeout value.
///
public static TimeSpan? GetTimeoutValue(TimeSpan? clientTimeout, TimeSpan? requestTimeout)
{
return requestTimeout.HasValue ? requestTimeout
: (clientTimeout.HasValue ? clientTimeout : null);
}
#if NETSTANDARD || PCL
#if NETSTANDARD
bool cacheHttpClient = true;
#else
bool cacheHttpClient = false;
#endif
///
///
/// This is a switch used for performance testing and is not intended for production applications
/// to change. This switch may be removed in a future version of the SDK as the .NET Core platform matures.
///
///
/// If true, the HttpClient is cached and reused for every request made by the service client
/// and shared with other service clients.
///
///
/// For the .NET Core platform this is default to true because the HttpClient manages the connection
/// pool.
///
///
public bool CacheHttpClient
{
get { return this.cacheHttpClient; }
set { this.cacheHttpClient = value; }
}
int? _httpClientCacheSize;
///
/// If CacheHttpClient is set to true then HttpClientCacheSize controls the number of HttpClients cached.
///
/// On Windows the default value is 1 since the underlying native implementation does not have throttling constraints
/// like the non Windows Curl based implementation. For non Windows based platforms the default is the value return from
/// System.Environment.ProcessorCount.
///
///
public int HttpClientCacheSize
{
get
{
if(this._httpClientCacheSize.HasValue)
{
return this._httpClientCacheSize.Value;
}
#if NETSTANDARD
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return 1;
}
return Environment.ProcessorCount;
#else
return 1;
#endif
}
set
{
this._httpClientCacheSize = value;
}
}
#endif
///
/// Overrides the default read-write timeout value.
/// On Unity platform, this value is not used as Unity HTTP client does not support timeouts, so
///
///
///
/// If the value is set, the value is assigned to the ReadWriteTimeout property of the HttpWebRequest object used
/// to send requests.
///
/// The timeout specified is null.
/// The timeout specified is less than or equal to zero and is not Infinite.
///
///
public TimeSpan? ReadWriteTimeout
{
get { return this.readWriteTimeout; }
set
{
ValidateTimeout(value);
this.readWriteTimeout = value;
}
}
}
}