/******************************************************************************* * Copyright 2008-2012 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. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET * */ using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Reflection; using System.Text; using System.Threading; using System.Xml; using System.Xml.Linq; using Amazon.Runtime; using System.Globalization; using Amazon.Runtime.Internal.Util; using Amazon.Util.Internal; using ThirdParty.Json.LitJson; using System.Linq; #if UNITY using UnityEngine; using Amazon.Runtime.Internal; #endif namespace Amazon.Internal { public class RegionEndpointProviderV2 : IRegionEndpointProvider { /// /// Allows to configure the proxy used for HTTP requests. The default value is null. /// public static IWebProxy Proxy { get; set; } public IEnumerable AllRegionEndpoints { get { return RegionEndpoint.EnumerableAllRegions as IEnumerable; } } public IRegionEndpoint GetRegionEndpoint(string regionName) { return RegionEndpoint.GetBySystemName(regionName); } #region RegionEndpoint /// /// This class contains the endpoints available to the AWS clients. The static constants representing the /// regions can be used while constructing the AWS client instead of looking up the exact endpoint URL. /// public class RegionEndpoint : IRegionEndpoint { #if NETSTANDARD // The shared endpoint rules used by other AWS SDKs. const string REGIONS_FILE = "Core.endpoints.json"; // The .NET SDK specific customization to support legacy decisions made for endpoints. const string REGIONS_CUSTOMIZATIONS_FILE = "Core.endpoints.customizations.json"; #else // The shared endpoint rules used by other AWS SDKs. const string REGIONS_FILE = "Amazon.endpoints.json"; // The .NET SDK specific customization to support legacy decisions made for endpoints. const string REGIONS_CUSTOMIZATIONS_FILE = "Amazon.endpoints.customizations.json"; #endif const string DEFAULT_RULE = "*/*"; #region Statics static Dictionary _documentEndpoints; const int MAX_DOWNLOAD_RETRIES = 3; static bool loaded = false; static readonly object LOCK_OBJECT = new object(); // Dictionary of regions by system name private static Dictionary hashBySystemName = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets the endpoint for a service in a region. /// /// The services system name. /// If true returns the endpoint for dualstack /// Thrown when the request service does not have a valid endpoint in the region. /// public Amazon.RegionEndpoint.Endpoint GetEndpointForService(string serviceName, bool dualStack) { if (!RegionEndpoint.loaded) RegionEndpoint.LoadEndpointDefinitions(); var rule = GetEndpointRule(serviceName); var endpointTemplate = rule["endpoint"].ToString(); if (dualStack) { // We need special handling for S3's s3.amazonaws.com endpoint, which doesn't // support dualstack (need to transform to s3.dualstack.us-east-1.amazonaws.com). // Other endpoints that begin s3-* need to transform to s3.* for dualstack support. // S3's 'external' endpoints do not support dualstack and should not be transformed. if (serviceName.Equals("s3", StringComparison.OrdinalIgnoreCase)) { if (endpointTemplate.Equals("s3.amazonaws.com", StringComparison.OrdinalIgnoreCase)) endpointTemplate = "s3.dualstack.us-east-1.amazonaws.com"; else { var isExternalEndpoint = endpointTemplate.StartsWith("s3-external-", StringComparison.OrdinalIgnoreCase); if (!isExternalEndpoint) { // transform fixed s3- to s3. and then onto s3.dualstack., // bypassing endpoints that do not start with the expected tags. if (endpointTemplate.StartsWith("s3-", StringComparison.OrdinalIgnoreCase)) endpointTemplate = "s3." + endpointTemplate.Substring(3); if (endpointTemplate.StartsWith("s3.", StringComparison.OrdinalIgnoreCase)) endpointTemplate = endpointTemplate.Replace("s3.", "s3.dualstack."); } } } else endpointTemplate = endpointTemplate.Replace("{region}", "dualstack.{region}"); } var hostName = endpointTemplate.Replace("{region}", this.SystemName).Replace("{service}", serviceName); string signatureVersion = null; if (rule["signature-version"] != null) signatureVersion = rule["signature-version"].ToString(); string authRegion; if (rule["auth-region"] != null) authRegion = rule["auth-region"].ToString(); else authRegion = Amazon.Util.AWSSDKUtils.DetermineRegion(hostName); if (string.Equals(authRegion, this.SystemName, StringComparison.OrdinalIgnoreCase)) authRegion = null; return new Amazon.RegionEndpoint.Endpoint(hostName, authRegion, signatureVersion); } JsonData GetEndpointRule(string serviceName) { JsonData rule = null; if (_documentEndpoints.TryGetValue(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", this.SystemName, serviceName), out rule)) return rule; if (_documentEndpoints.TryGetValue(string.Format(CultureInfo.InvariantCulture, "{0}/*", this.SystemName), out rule)) return rule; if (_documentEndpoints.TryGetValue(string.Format(CultureInfo.InvariantCulture, "*/{0}", serviceName), out rule)) return rule; return _documentEndpoints[DEFAULT_RULE]; } // Creates a new RegionEndpoint and stores it in the hash private static RegionEndpoint GetEndpoint(string systemName, string displayName) { RegionEndpoint regionEndpoint = null; lock (hashBySystemName) { if (hashBySystemName.TryGetValue(systemName, out regionEndpoint)) return regionEndpoint; regionEndpoint = new RegionEndpoint(systemName, displayName); hashBySystemName.Add(regionEndpoint.SystemName, regionEndpoint); } return regionEndpoint; } /// /// Enumerate through all the regions. /// public static IEnumerable EnumerableAllRegions { get { if (!RegionEndpoint.loaded) RegionEndpoint.LoadEndpointDefinitions(); lock (hashBySystemName) { return hashBySystemName.Values.ToList(); } } } /// /// Gets the region based on its system name like "us-west-1" /// /// The system name of the service like "us-west-1" /// public static RegionEndpoint GetBySystemName(string systemName) { if (!RegionEndpoint.loaded) RegionEndpoint.LoadEndpointDefinitions(); RegionEndpoint region = null; lock(hashBySystemName) { if (!hashBySystemName.TryGetValue(systemName, out region)) { // explicit namespace to avoid collision with UnityEngine.Logger var logger = Amazon.Runtime.Internal.Util.Logger.GetLogger(typeof(RegionEndpoint)); logger.InfoFormat("Region system name {0} was not found in region data bundled with SDK; assuming new region.", systemName); if (systemName.StartsWith("cn-", StringComparison.Ordinal)) return GetEndpoint(systemName, "China (Unknown)"); return GetEndpoint(systemName, "Unknown"); } } return region; } static void LoadEndpointDefinitions() { LoadEndpointDefinitions(AWSConfigs.EndpointDefinition); } public static void LoadEndpointDefinitions(string endpointsPath) { lock (LOCK_OBJECT) { if (RegionEndpoint.loaded) return; _documentEndpoints = new Dictionary(); if (string.IsNullOrEmpty(endpointsPath)) { #if BCL || (NETSTANDARD && !NETSTANDARD13) if (TryLoadEndpointDefinitionsFromAssemblyDir()) { RegionEndpoint.loaded = true; return; } #endif LoadEndpointDefinitionsFromEmbeddedResource(); } #if !UNITY else if (endpointsPath.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { LoadEndpointDefinitionFromWeb(endpointsPath); } #endif #if BCL || NETSTANDARD else { LoadEndpointDefinitionFromFilePath(endpointsPath); } #endif RegionEndpoint.loaded = true; } } static void ReadEndpointFile(Stream stream) { using (var reader = new StreamReader(stream)) { var root = JsonMapper.ToObject(reader); var endpoints = root["endpoints"]; foreach (var ruleName in endpoints.PropertyNames) { _documentEndpoints[ruleName] = endpoints[ruleName]; } } } static void LoadEndpointDefinitionsFromEmbeddedResource() { using (var stream = Amazon.Util.Internal.TypeFactory.GetTypeInfo(typeof(RegionEndpoint)).Assembly.GetManifestResourceStream(REGIONS_FILE)) { ReadEndpointFile(stream); } using (var stream = Amazon.Util.Internal.TypeFactory.GetTypeInfo(typeof(RegionEndpoint)).Assembly.GetManifestResourceStream(REGIONS_CUSTOMIZATIONS_FILE)) { ReadEndpointFile(stream); } } #if BCL || (NETSTANDARD && !NETSTANDARD13) static bool TryLoadEndpointDefinitionsFromAssemblyDir() { string endpointsFile; try { var assembly = typeof(Amazon.RegionEndpoint).Assembly; var codeBase = assembly.CodeBase; if (string.IsNullOrEmpty(codeBase)) return false; var uri = new Uri(codeBase); var dirPath = Path.GetDirectoryName(uri.LocalPath); var dirInfo = new DirectoryInfo(dirPath); if (!dirInfo.Exists) return false; var files = dirInfo.GetFiles(REGIONS_FILE, SearchOption.TopDirectoryOnly); if (files.Length != 1) return false; endpointsFile = files[0].FullName; } catch { endpointsFile = null; } if (string.IsNullOrEmpty(endpointsFile)) return false; LoadEndpointDefinitionFromFilePath(endpointsFile); return true; } #endif #if BCL || NETSTANDARD static void LoadEndpointDefinitionFromFilePath(string path) { if (!System.IO.File.Exists(path)) throw new AmazonServiceException(string.Format(CultureInfo.InvariantCulture, "Local endpoint configuration file {0} override was not found.", path)); using (var stream = File.OpenRead(path)) { ReadEndpointFile(stream); } } #endif #if !UNITY static void LoadEndpointDefinitionFromWeb(string url) { int retries = 0; while (retries < MAX_DOWNLOAD_RETRIES) { try { using (var stream = Amazon.Util.AWSSDKUtils.OpenStream(new Uri(url), Proxy)) { ReadEndpointFile(stream); return; } } catch (Exception e) { retries++; if (retries == MAX_DOWNLOAD_RETRIES) throw new AmazonServiceException(string.Format(CultureInfo.InvariantCulture, "Error downloading regions definition file from {0}.", url), e); } int delay = (int)(Math.Pow(4, retries) * 100); delay = Math.Min(delay, 30 * 1000); Util.AWSSDKUtils.Sleep(delay); } } #endif /// /// This is a testing method and should not be called by production applications. /// public static void UnloadEndpointDefinitions() { lock (LOCK_OBJECT) { _documentEndpoints.Clear(); RegionEndpoint.loaded = false; } } #endregion private RegionEndpoint(string systemName, string displayName) { this.SystemName = systemName; this.DisplayName = displayName; } /// /// Gets the system name of a region. /// public string SystemName { get; private set; } /// /// Gets the display name of a region. /// public string DisplayName { get; private set; } #region IRegionEndpoint public string RegionName { get { return SystemName; } } #endregion public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", this.DisplayName, this.SystemName); } } #endregion } }