• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.core.internal.handler;
17 
18 import java.net.URI;
19 import java.time.Duration;
20 import java.util.Optional;
21 import java.util.function.BiFunction;
22 import software.amazon.awssdk.annotations.SdkInternalApi;
23 import software.amazon.awssdk.core.CredentialType;
24 import software.amazon.awssdk.core.Response;
25 import software.amazon.awssdk.core.SdkRequest;
26 import software.amazon.awssdk.core.SdkResponse;
27 import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
28 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
29 import software.amazon.awssdk.core.client.config.SdkClientOption;
30 import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
31 import software.amazon.awssdk.core.exception.SdkClientException;
32 import software.amazon.awssdk.core.http.ExecutionContext;
33 import software.amazon.awssdk.core.http.HttpResponseHandler;
34 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
35 import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain;
36 import software.amazon.awssdk.core.interceptor.InterceptorContext;
37 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
38 import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
39 import software.amazon.awssdk.core.internal.InternalCoreExecutionAttribute;
40 import software.amazon.awssdk.core.internal.io.SdkLengthAwareInputStream;
41 import software.amazon.awssdk.core.internal.util.MetricUtils;
42 import software.amazon.awssdk.core.metrics.CoreMetric;
43 import software.amazon.awssdk.core.signer.Signer;
44 import software.amazon.awssdk.core.sync.RequestBody;
45 import software.amazon.awssdk.http.ContentStreamProvider;
46 import software.amazon.awssdk.http.SdkHttpFullRequest;
47 import software.amazon.awssdk.http.SdkHttpFullResponse;
48 import software.amazon.awssdk.http.SdkHttpRequest;
49 import software.amazon.awssdk.metrics.MetricCollector;
50 import software.amazon.awssdk.utils.Pair;
51 import software.amazon.awssdk.utils.StringUtils;
52 
53 @SdkInternalApi
54 public abstract class BaseClientHandler {
55     private SdkClientConfiguration clientConfiguration;
56 
BaseClientHandler(SdkClientConfiguration clientConfiguration)57     protected BaseClientHandler(SdkClientConfiguration clientConfiguration) {
58         this.clientConfiguration = clientConfiguration;
59     }
60 
61     /**
62      * Finalize {@link SdkHttpFullRequest} by running beforeMarshalling, afterMarshalling,
63      * modifyHttpRequest, modifyHttpContent and modifyAsyncHttpContent interceptors
64      */
finalizeSdkHttpFullRequest( ClientExecutionParams<InputT, OutputT> executionParams, ExecutionContext executionContext, InputT inputT, SdkClientConfiguration clientConfiguration)65     static <InputT extends SdkRequest, OutputT> InterceptorContext finalizeSdkHttpFullRequest(
66         ClientExecutionParams<InputT, OutputT> executionParams,
67         ExecutionContext executionContext, InputT inputT,
68         SdkClientConfiguration clientConfiguration) {
69 
70         runBeforeMarshallingInterceptors(executionContext);
71 
72         Pair<SdkHttpFullRequest, Duration> measuredMarshall = MetricUtils.measureDuration(() ->
73                 executionParams.getMarshaller().marshall(inputT));
74 
75         executionContext.metricCollector().reportMetric(CoreMetric.MARSHALLING_DURATION, measuredMarshall.right());
76 
77         SdkHttpFullRequest request = measuredMarshall.left();
78 
79         request = modifyEndpointHostIfNeeded(request, clientConfiguration, executionParams);
80 
81         addHttpRequest(executionContext, request);
82         runAfterMarshallingInterceptors(executionContext);
83         return runModifyHttpRequestAndHttpContentInterceptors(executionContext);
84     }
85 
runBeforeMarshallingInterceptors(ExecutionContext executionContext)86     private static void runBeforeMarshallingInterceptors(ExecutionContext executionContext) {
87         executionContext.interceptorChain().beforeMarshalling(executionContext.interceptorContext(),
88                                                               executionContext.executionAttributes());
89     }
90 
91     /**
92      * Modifies the given {@link SdkHttpFullRequest} with new host if host prefix is enabled and set.
93      */
modifyEndpointHostIfNeeded(SdkHttpFullRequest originalRequest, SdkClientConfiguration clientConfiguration, ClientExecutionParams executionParams)94     private static SdkHttpFullRequest modifyEndpointHostIfNeeded(SdkHttpFullRequest originalRequest,
95                                                                  SdkClientConfiguration clientConfiguration,
96                                                                  ClientExecutionParams executionParams) {
97         if (executionParams.discoveredEndpoint() != null) {
98             URI discoveredEndpoint = executionParams.discoveredEndpoint();
99             executionParams.putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true);
100             return originalRequest.toBuilder().host(discoveredEndpoint.getHost()).port(discoveredEndpoint.getPort()).build();
101         }
102 
103         Boolean disableHostPrefixInjection = clientConfiguration.option(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION);
104         if ((disableHostPrefixInjection != null && disableHostPrefixInjection.equals(Boolean.TRUE)) ||
105             StringUtils.isEmpty(executionParams.hostPrefixExpression())) {
106             return originalRequest;
107         }
108 
109         return originalRequest.toBuilder()
110                               .host(executionParams.hostPrefixExpression() + originalRequest.host())
111                               .build();
112     }
113 
addHttpRequest(ExecutionContext executionContext, SdkHttpFullRequest request)114     private static void addHttpRequest(ExecutionContext executionContext, SdkHttpFullRequest request) {
115         InterceptorContext interceptorContext = executionContext.interceptorContext();
116 
117         Optional<ContentStreamProvider> contentStreamProvider = request.contentStreamProvider();
118         if (contentStreamProvider.isPresent()) {
119             interceptorContext = interceptorContext.copy(b -> b.httpRequest(request)
120                                                                .requestBody(getBody(request)));
121         } else {
122             interceptorContext = interceptorContext.copy(b -> b.httpRequest(request));
123         }
124 
125         executionContext.interceptorContext(interceptorContext);
126     }
127 
getBody(SdkHttpFullRequest request)128     private static RequestBody getBody(SdkHttpFullRequest request) {
129         Optional<ContentStreamProvider> contentStreamProviderOptional = request.contentStreamProvider();
130         if (contentStreamProviderOptional.isPresent()) {
131             Optional<String> contentLengthOptional = request.firstMatchingHeader("Content-Length");
132             long contentLength = Long.parseLong(contentLengthOptional.orElse("0"));
133             String contentType = request.firstMatchingHeader("Content-Type").orElse("");
134 
135             // Enforce the content length specified only if it was present on the request (and not the default).
136             ContentStreamProvider streamProvider = contentStreamProviderOptional.get();
137             if (contentLengthOptional.isPresent()) {
138                 ContentStreamProvider toWrap = contentStreamProviderOptional.get();
139                 streamProvider = () -> new SdkLengthAwareInputStream(toWrap.newStream(), contentLength);
140             }
141 
142             return RequestBody.fromContentProvider(streamProvider,
143                                                    contentLength,
144                                                    contentType);
145         }
146 
147         return null;
148     }
149 
runAfterMarshallingInterceptors(ExecutionContext executionContext)150     private static void runAfterMarshallingInterceptors(ExecutionContext executionContext) {
151         executionContext.interceptorChain().afterMarshalling(executionContext.interceptorContext(),
152                                                              executionContext.executionAttributes());
153     }
154 
runModifyHttpRequestAndHttpContentInterceptors(ExecutionContext executionContext)155     private static InterceptorContext runModifyHttpRequestAndHttpContentInterceptors(ExecutionContext executionContext) {
156         InterceptorContext interceptorContext =
157             executionContext.interceptorChain().modifyHttpRequestAndHttpContent(executionContext.interceptorContext(),
158                                                                                 executionContext.executionAttributes());
159         executionContext.interceptorContext(interceptorContext);
160         return interceptorContext;
161     }
162 
163     /**
164      * Run afterUnmarshalling and modifyResponse interceptors.
165      */
166     private static <OutputT extends SdkResponse> BiFunction<OutputT, SdkHttpFullResponse, OutputT>
runAfterUnmarshallingInterceptors(ExecutionContext context)167         runAfterUnmarshallingInterceptors(ExecutionContext context) {
168 
169         return (input, httpFullResponse) -> {
170             // Update interceptor context to include response
171             InterceptorContext interceptorContext =
172                 context.interceptorContext().copy(b -> b.response(input));
173 
174             context.interceptorChain().afterUnmarshalling(interceptorContext, context.executionAttributes());
175 
176             interceptorContext = context.interceptorChain().modifyResponse(interceptorContext, context.executionAttributes());
177 
178             // Store updated context
179             context.interceptorContext(interceptorContext);
180 
181             return (OutputT) interceptorContext.response();
182         };
183     }
184 
185     private static <OutputT extends SdkResponse> BiFunction<OutputT, SdkHttpFullResponse, OutputT>
attachHttpResponseToResult()186         attachHttpResponseToResult() {
187 
188         return ((response, httpFullResponse) -> (OutputT) response.toBuilder().sdkHttpResponse(httpFullResponse).build());
189     }
190 
191     // This method is only called from tests, since the subclasses in aws-core override it.
192     protected <InputT extends SdkRequest, OutputT extends SdkResponse> ExecutionContext
invokeInterceptorsAndCreateExecutionContext( ClientExecutionParams<InputT, OutputT> params)193         invokeInterceptorsAndCreateExecutionContext(
194         ClientExecutionParams<InputT, OutputT> params) {
195         SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(params);
196         SdkRequest originalRequest = params.getInput();
197 
198         ExecutionAttributes executionAttributes = params.executionAttributes();
199         executionAttributes
200             .putAttribute(InternalCoreExecutionAttribute.EXECUTION_ATTEMPT, 1)
201             .putAttribute(SdkExecutionAttribute.SERVICE_CONFIG,
202                           clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION))
203             .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfiguration.option(SdkClientOption.SERVICE_NAME))
204             .putAttribute(SdkExecutionAttribute.PROFILE_FILE,
205                           clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ?
206                           clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER).get() : null)
207             .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER,
208                           clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER))
209             .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfiguration.option(SdkClientOption.PROFILE_NAME));
210 
211         ExecutionInterceptorChain interceptorChain =
212             new ExecutionInterceptorChain(clientConfiguration.option(SdkClientOption.EXECUTION_INTERCEPTORS));
213 
214         InterceptorContext interceptorContext = InterceptorContext.builder()
215                                                                   .request(originalRequest)
216                                                                   .build();
217 
218         interceptorChain.beforeExecution(interceptorContext, executionAttributes);
219         interceptorContext = interceptorChain.modifyRequest(interceptorContext, executionAttributes);
220 
221         MetricCollector metricCollector = resolveMetricCollector(params);
222 
223         return ExecutionContext.builder()
224                                .interceptorChain(interceptorChain)
225                                .interceptorContext(interceptorContext)
226                                .executionAttributes(executionAttributes)
227                                .signer(clientConfiguration.option(SdkAdvancedClientOption.SIGNER))
228                                .metricCollector(metricCollector)
229                                .build();
230     }
231 
isCalculateCrc32FromCompressedData()232     protected boolean isCalculateCrc32FromCompressedData() {
233         return clientConfiguration.option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED);
234     }
235 
validateSigningConfiguration(SdkHttpRequest request, Signer signer)236     protected void validateSigningConfiguration(SdkHttpRequest request, Signer signer) {
237         if (signer == null) {
238             return;
239         }
240 
241         if (signer.credentialType() != CredentialType.TOKEN) {
242             return;
243         }
244 
245         URI endpoint = request.getUri();
246         if (!"https".equals(endpoint.getScheme())) {
247             throw SdkClientException.create("Cannot use bearer token signer with a plaintext HTTP endpoint: " + endpoint);
248         }
249     }
250 
resolveRequestConfiguration(ClientExecutionParams<?, ?> params)251     protected SdkClientConfiguration resolveRequestConfiguration(ClientExecutionParams<?, ?> params) {
252         SdkClientConfiguration config =  params.requestConfiguration();
253         if (config != null) {
254             return config;
255         }
256         return clientConfiguration;
257     }
258 
259     /**
260      * Decorate response handlers by running after unmarshalling Interceptors and adding http response metadata.
261      */
decorateResponseHandlers( HttpResponseHandler<OutputT> delegate, ExecutionContext executionContext)262     <OutputT extends SdkResponse> HttpResponseHandler<OutputT> decorateResponseHandlers(
263         HttpResponseHandler<OutputT> delegate, ExecutionContext executionContext) {
264 
265         return resultTransformationResponseHandler(delegate, responseTransformations(executionContext));
266     }
267 
decorateSuccessResponseHandlers( HttpResponseHandler<Response<OutputT>> delegate, ExecutionContext executionContext)268     <OutputT extends SdkResponse> HttpResponseHandler<Response<OutputT>> decorateSuccessResponseHandlers(
269         HttpResponseHandler<Response<OutputT>> delegate, ExecutionContext executionContext) {
270 
271         return successTransformationResponseHandler(delegate, responseTransformations(executionContext));
272     }
273 
successTransformationResponseHandler( HttpResponseHandler<Response<OutputT>> responseHandler, BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer)274     <OutputT extends SdkResponse> HttpResponseHandler<Response<OutputT>> successTransformationResponseHandler(
275         HttpResponseHandler<Response<OutputT>> responseHandler,
276         BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer) {
277 
278         return (response, executionAttributes) -> {
279             Response<OutputT> delegateResponse = responseHandler.handle(response, executionAttributes);
280 
281             if (delegateResponse.isSuccess()) {
282                 return delegateResponse.toBuilder()
283                                        .response(successTransformer.apply(delegateResponse.response(), response))
284                                        .build();
285             } else {
286                 return delegateResponse;
287             }
288         };
289     }
290 
291     <OutputT extends SdkResponse> HttpResponseHandler<OutputT> resultTransformationResponseHandler(
292         HttpResponseHandler<OutputT> responseHandler,
293         BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer) {
294 
295         return (response, executionAttributes) -> {
296             OutputT delegateResponse = responseHandler.handle(response, executionAttributes);
297             return successTransformer.apply(delegateResponse, response);
298         };
299     }
300 
301     static void validateCombinedResponseHandler(ClientExecutionParams<?, ?> executionParams) {
302         if (executionParams.getCombinedResponseHandler() != null) {
303             if (executionParams.getResponseHandler() != null) {
304                 throw new IllegalArgumentException("Only one of 'combinedResponseHandler' and 'responseHandler' may "
305                                                    + "be specified in a ClientExecutionParams object");
306             }
307 
308             if (executionParams.getErrorResponseHandler() != null) {
309                 throw new IllegalArgumentException("Only one of 'combinedResponseHandler' and 'errorResponseHandler' "
310                                                    + "may be specified in a ClientExecutionParams object");
311             }
312         }
313     }
314 
315     /**
316      * Returns the composition of 'runAfterUnmarshallingInterceptors' and 'attachHttpResponseToResult' response
317      * transformations as a single transformation that should be applied to all responses.
318      */
319     private static <T extends SdkResponse> BiFunction<T, SdkHttpFullResponse, T>
320         responseTransformations(ExecutionContext executionContext) {
321 
322         return composeResponseFunctions(runAfterUnmarshallingInterceptors(executionContext),
323                                         attachHttpResponseToResult());
324     }
325 
326     /**
327      * Composes two functions passing the result of the first function as the first argument of the second function
328      * and the same second argument to both functions. This is used by response transformers to chain together and
329      * pass through a persistent SdkHttpFullResponse object as a second arg turning them effectively into a single
330      * response transformer.
331      * <p>
332      * So given f1(x, y) and f2(x, y) where x is typically OutputT and y is typically SdkHttpFullResponse the composed
333      * function would be f12(x, y) = f2(f1(x, y), y).
334      */
335     private static <T, R> BiFunction<T, R, T> composeResponseFunctions(BiFunction<T, R, T> function1,
336                                                                        BiFunction<T, R, T> function2) {
337         return (x, y) -> function2.apply(function1.apply(x, y), y);
338     }
339 
340     private MetricCollector resolveMetricCollector(ClientExecutionParams<?, ?> params) {
341         MetricCollector metricCollector = params.getMetricCollector();
342         if (metricCollector == null) {
343             metricCollector = MetricCollector.create("ApiCall");
344         }
345         return metricCollector;
346     }
347 }
348