/* * Copyright 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.Net; namespace Amazon.Runtime.Internal { /// /// This handler resolves the endpoint to be used for the current request. /// public class EndpointDiscoveryHandler : PipelineHandler { //The status code returned from a service request when an invalid endpoint is used. private const int INVALID_ENDPOINT_EXCEPTION_STATUSCODE = 421; /// /// Calls pre invoke logic before calling the next handler /// in the pipeline. /// /// The execution context which contains both the /// requests and response context. public override void InvokeSync(IExecutionContext executionContext) { var requestContext = executionContext.RequestContext; var regionalEndpoint = requestContext.Request.Endpoint; PreInvoke(executionContext); try { base.InvokeSync(executionContext); return; } catch (Exception exception) { if (IsInvalidEndpointException(exception)) { EvictCacheKeyForRequest(requestContext, regionalEndpoint); } throw; } } #if AWS_ASYNC_API /// /// Calls pre invoke logic before calling the next handler /// in the pipeline. /// /// The response type for the current request. /// The execution context, it contains the /// request and response context. /// A task that represents the asynchronous operation. public override async System.Threading.Tasks.Task InvokeAsync(IExecutionContext executionContext) { var requestContext = executionContext.RequestContext; var regionalEndpoint = requestContext.Request.Endpoint; PreInvoke(executionContext); try { return await base.InvokeAsync(executionContext).ConfigureAwait(false); } catch (Exception exception) { var capturedException = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception); if (IsInvalidEndpointException(capturedException.SourceException)) { EvictCacheKeyForRequest(requestContext, regionalEndpoint); } capturedException.Throw(); } throw new AmazonClientException("Neither a response was returned nor an exception was thrown in the Runtime EndpointDiscoveryResolver."); } #elif AWS_APM_API /// /// Invokes the inner handler /// /// The execution context which contains both the /// requests and response context. protected override void InvokeAsyncCallback(IAsyncExecutionContext executionContext) { try { var requestContext = executionContext.RequestContext; var responseContext = executionContext.ResponseContext; var regionalEndpoint = requestContext.Request.Endpoint; var exception = responseContext.AsyncResult.Exception; if (exception != null) { if (IsInvalidEndpointException(exception)) { EvictCacheKeyForRequest(requestContext, regionalEndpoint); } } else { PreInvoke(ExecutionContext.CreateFromAsyncContext(executionContext)); } } catch(Exception e) { executionContext.ResponseContext.AsyncResult.Exception = e; } finally { // Call outer handler base.InvokeAsyncCallback(executionContext); } } #endif /// /// Resolves the endpoint to be used for the current request /// before invoking the next handler. /// /// The execution context, it contains the /// request and response context. protected static void PreInvoke(IExecutionContext executionContext) { DiscoverEndpoints(executionContext.RequestContext, false); } public static void EvictCacheKeyForRequest(IRequestContext requestContext, Uri regionalEndpoint) { DiscoverEndpoints(requestContext, true); requestContext.Request.Endpoint = regionalEndpoint; } public static void DiscoverEndpoints(IRequestContext requestContext, bool evictCacheKey) { var discoveryEndpoints = ProcessEndpointDiscovery(requestContext, evictCacheKey, requestContext.Request.Endpoint); if (discoveryEndpoints != null) { foreach (var endpoint in discoveryEndpoints) { //A null address can occur if this endpoint does not require endpoint discovery //and we couldn't get an endpoint back during an asynchronous discovery //attempt. The null address endpoint will be evicted after 60 seconds but will //prevent multiple server requests during this time. if(endpoint.Address == null) { continue; } //Only the first endpoint should be used currently requestContext.Request.Endpoint = new Uri(endpoint.Address); break; } } } private static IEnumerable ProcessEndpointDiscovery(IRequestContext requestContext, bool evictCacheKey, Uri evictUri) { var options = requestContext.Options; if (options.EndpointDiscoveryMarshaller != null && options.EndpointOperation != null && requestContext.ImmutableCredentials != null) { //Endpoint discovery is supported by this operation and we have an endpoint operation available to use var endpointDiscoveryData = options.EndpointDiscoveryMarshaller.Marshall(requestContext.OriginalRequest); var operationName = string.Empty; if (endpointDiscoveryData.Identifiers != null && endpointDiscoveryData.Identifiers.Count > 0) { operationName = OperationNameFromRequestName(requestContext.RequestName); } return options.EndpointOperation(new EndpointOperationContext(requestContext.ImmutableCredentials.AccessKey, operationName, endpointDiscoveryData, evictCacheKey, evictUri)); } return null; } private static string OperationNameFromRequestName(string requestName) { if (requestName.EndsWith("Request", StringComparison.Ordinal)) { return requestName.Substring(0, requestName.Length - 7); } return requestName; } private static bool IsInvalidEndpointException(Exception exception) { var serviceException = exception as AmazonServiceException; if (serviceException != null && serviceException.StatusCode == (HttpStatusCode)INVALID_ENDPOINT_EXCEPTION_STATUSCODE) { return true; } return false; } } }